Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Costas-Tyros_rus_MS

.pdf
Скачиваний:
31
Добавлен:
14.04.2015
Размер:
5 Мб
Скачать
☆

Глава 20: Баги

Старые компьютеры были основаны на реле, кото рые представляют собой громоздкие электрические уст ройства, обычно содержащие в себе электромагнит, ко торый активируется напряжением в одной цепи для включения или выключения другой цепи. Компьютеры, сделанные из подобных вещей, были огромными, мед ленными и ненадёжными. Поэтому, 9го сентября 1945 года моль влетела в одно из реле компьютера Harvard Mark II и угробила его. C того времени bug1 – баг стало стандартным словом для обозначения ошибки, которая препятствует компьютеру работать как предназначено.

Из за багов, компилятор Visual Prolog часто возвращает сообщения об ошибках, вместо того, что бы создавать код и запускать соответствующие программы. На самом деле, компилятор Visual Prolog выдаёт больше сообщений об ошибках, чем любой другой язык, кроме, возможно, Clean. Студенты не ценят это постоянное ворчание об ошибках, и редко пытаются проанализировать сообщения об ошибках, но поверьте мне, вам следует поблагодарить разработчиков такого компилятора, который даёт вам шанс вычистить все пятна, которые могут повредить вашей программе. Как я уже сказал, Visual Prolog хорош в локализации багов. Несмотря на это, если вы не научитесь разбираться с сооб щениями об ошибках, отладка покажется вам случайным хождением, которое почти никогда не ве дёт к успеху. Поэтому, в этой главе вы научитесь интерпретировать сообщения компилятора.

20.1.Ошибки типа

В программе ниже нет багов; однако, если вам нужно использовать беззнаковое целое, которое получается от функции факториала для, скажем, вычисления двоичного числа, компилятор может отказаться выполнять это.

implement main open core clauses

classInfo("main", "bugs"). class predicates

fact:(unsigned) > unsigned. clauses

fact(N) = F : if N < 1 then F=1 else F=N*fact(N 1) end if.

run(): console::init(), stdio::write("N: "), N= stdio::read(),

stdio::write(fact(N)), stdio::nl. end implement main

goal mainExe::run(main::run).

Например, если вы попробуете откомпилировать программу с рис. 20.1, вы получите следующее сообщение об ошибке:

error c504: The expression has type '::integer', which is incompatible with the type '::unsigned'

1 переводится с английского как «жук», иногда «насекомое».

131

Ошибка исчезнет, если вы явно преобразуете каждый входной аргумент binum/3 в беззнаковое целое; вы также должны преобразовать выходной аргумент факториала в целое.

binum(N, P, R) :

N1= convert(unsigned, N), P1= convert(unsigned, P),

R = convert(integer, fact(N1) div (fact(P1)*fact(N1 P1))).

implement main open core

clauses

classInfo("main", "bugs").

class predicates fact:(unsigned) > unsigned.

binum:(integer, integer, integer) procedure (i, i, o).

clauses

fact(N) = F : if N < 1 then F=1 else F=N*fact(N 1) end if. binum(N, P, R) : R = fact(N) div (fact(P)*fact(N P)).

run(): console::init(),

stdio::write("N: "), N= stdio::read(), stdio::write("P: "), P= stdio::read(), binum(N, P, R), stdio::write(R), stdio::nl.

end implement main

goal mainExe::run(main::run).

Рисунок 20.1. Забагованная программа.

20.2.Не процедуравнутри процедуры

Это ещё один баг, который доставляет много сложностей начинающим. Visual Prolog объявляет

некоторые предикаты как процедуры. Например, в случае предиката run(), который имеется во всех консольных программах. Это означает, что вы не можете вызвать nondeterm предикат из run(). Та ким образом, если вы попытаетесь откомпилировать программу, приведённую на рис. 20.2, вы полу чите следующее сообщение об ошибке:

error c631: The predicate 'nonprocedure::run/0',

which is declared as 'procedure', is actually 'nondeterm'

Это правда, что фактовый предикат weight/2 пронумерован по его целочисленному аргументу. Что означает, что получив свой целочисленный аргумент, weight/2 должен действовать как проце дура. Однако Пролог глух к принятию желаемого за действительное; вам необходимо определить процедуру getweight/2, которая будет извлекать значения веса.

Ниже вы можете увидеть, как getweight может быть определен. Используйте это как модель для выхода из ситуаций, где вам надо вызвать nondeterm предикат из процедуры.

132

implement main class facts

weight:(integer, real). class predicates

getweight:(integer, real) procedure (i, o). clauses

classInfo("main", "nonprocedure"). weight(0, 1.0).

weight(1, 2.0). weight(2, 3.5).

getweight(I, R) : weight(I, R), ! or R=0. run(): console::init(), getweight(1, X),

stdio::write(X), stdio::nl. end implement main

goal mainExe::run(main::run).

20.3.Неопределённые условия.

В Прологе есть конструкции, которые не принимают nondeterm предикаты. Например, условием конструкции if then else обязан быть determ предикат. Поэтому, программа на рис. 20.3 выведет

сообщение об ошибке:

error c634: The condition part of if then else may not have a backtrack point (almost determ)

Ошибка исчезнет, если вы определите member как determ предикат:

class predicates

member:(string, string*) determ. clauses

member(X, [X|_]) : !.

member(X, [_|Xs]) : member(X, Xs).

implement main class facts

weight:(integer, real). clauses

classInfo("main", "nonprocedure").

weight(0, 1.0). weight(1, 2.0). weight(2, 3.5).

run(): console::init(),

weight(1, X), stdio::write(X), stdio::nl. end implement main

goal mainExe::run(main::run).

Рисунок 20.2 Баг, вызванный недетерминистическим предикатом.

133

implement main class predicates

vowel:(string, string) procedure (i, o). member:(string, string*) nondeterm.

clauses

classInfo("main", "ifexample 1.0").

member(X, [X|_]).

member(X, [_|Xs]) : member(X, Xs).

vowel(X, Ans) : if member(X, ["a", "e", "i", "o", "u"]) then Ans= "yes" else Ans="no" end if.

run(): console::init(), vowel("e", Ans), stdio::write(Ans).

end implement main

goal mainExe::run(main::run).

Рисунок 20.3. Требуется детерминистический предикат.

20.4.Невозможноопределить тип

Есть случаи, когда необходимо определить тип до конкретизации переменной; уточнение типа может быть произведена процедурой

hasdomain(type, Var)

В бесполезной программе приведённой ниже, если убрать hasdomain(integer, X), то получим сообщение об ошибке, утверждающее, что тип терма X невозможно определить.

implement main clauses

classInfo("main", "vartest 1.0"). run() : console::init(),

hasdomain(integer, X), X= stdio::read(), stdio::write(X, X, X), stdio::nl.

end implement main

goal mainExe::run(main::run).

20.5.Направлениеввода вывода

Когда вы не объявляете направление ввода вывода, Пролог предполагает, что все аргументы яв ляются входными. Так как это упрощённое предположение очень часто бывает неверным, вы полу чите сообщение об ошибке, как в примере ниже.

implement main class predicates

factorial:(integer, integer). clauses

134

classInfo("main", "dataflow 1.0"). factorial(0, 1) : !.

factorial(N, N*F1) : factorial(N 1, F1).

run(): console::init(), N= toTerm(stdio::readLine()), factorial(N, F), stdio::write(F).

end implement main

goal mainExe::run(main::run).

Вы можете исправить баг, объявив factorial как входную/выходную процедуру.

class predicates

factorial:(integer, integer) procedure (i, o).

135

Глава 21: Менеджер баз данных

Эта глава показывает, как создать форму с полями для ввода и использовать их содержимое для заполнения базы данных. Однако, самое важное в том, что вы научитесь получать доступ к полям из формы. Окно сохраняется в факте переменной, чьё название вы должны узнать, и использовать для управления окном, как в примере ниже.

onSave(_Source) = button::defaultAction() : Key= keyvalue_ctl:getText(), Email= email_ctl:getText(), T= listEdit_ctl:getText(), emails::insert_info(Key, Email, T).

21.1.Менеджербазыданных

Базы данных очень полезны в любом коммерческом приложении. Если вы не особенно много знаете о базах данных, я сильно рекомендую, чтобы вы поискали в интернете или в вашей местной библиотеке книгу по этой теме. Основные понятия B+Tree базы данных таковы:

Indexes – Индексы, которые хранятся в специальной структуре данных под названием B+Tree. Всё, что вы должны знать про B+Tree это то, что они хранят вещи упорядоченно (например, в алфа витном порядке). Когда вещи упорядочены, как, например, слова в словаре, их легче искать.

Domains – Домены определяют структуру записей. В нашем примере, домен контакта определён

как

domains

contact=email(string, string, string).

References – Ссылки являются указателями на места, в которых хранятся записи. После этого краткого введения, давайте опишем программу.

•Создайте проект, под названием emailBook, с объектно ориентированным GUI

•Используя пункт File/Add из панели задач, добавьте пакет chainDB. Этот пакет находится внутри папки pfc в каталоге установки. Постройте приложение.

•Создайте класс emails в корне дерева проекта. Уберите Creates Objects. В разделе 21.2 вы найдёте листинги emails.cl и emails.pro, которые вы должны вставить в соответст вующие файлы. Постройте приложение.

•Добавьте новый элемент в меню задач приложения. Для того, чтобы это сделать, идите в окно дерева проекта и дважды щёлкните по TaskMenu.mnu. В диалоговом окне TaskMenu щёлкните правой кнопкой мыши и выберите New из появившегося меню. В соответст вующем поле напишите Create, что будет названием элемента меню. Постройте прило жение.

•Добавьте следующий код

class predicates createNewDB:(string FNAME).

predicates

onCreate : window::menuItemListener. clauses

onCreate(_Source, _MenuTag) :

FName= vpiCommonDialogs::getFileName("*.*", ["Email", "*.dbs"],

"Create", [], ".", _X), !,

createNewDB(FName). onCreate(_, _).

createNewDB(Fname) : file::existfile(Fname), !.

136

createNewDB(Fname) : emails::data_base_create(Fname).

к Project Window\TaskMenu.win\Code Expert\Menu\id_create.

Вы должны разрабатывать ваши программы шаг за шагом, проверяя, верен ли каждый новый шаг, перед тем, как двигаться дальше. Сейчас вы добавили новый элемент Create в меню TaskMe

nu.mnu. Вы также вставили код в onCreate, для чтения имени файла и создания соответствующей ба зы данных. Постройте приложение, запустите его и выберите пункт Create меню приложения. Когда диалоговое окно выбора файла спросит вас об имени файла, наберите mails.db. Если всё будет идти по сценарию, программа создаст файл mails.db. Когда вам нужно использовать базу данных, вам нужно её открыть. После её использования вы должны закрыть её, до того, как выключить компью тер. Чтобы удостовериться, что мы не забыли закрыть базу данных до выхода из приложения, давай те добавим фрагмент

clauses

onDestroy(_) : emails::data_base_close().

к Project Tree/TaskWindow.win/Code Expert/Window/onDestroy.

Теперь, когда вы уверены, что никто не забудет закрыть базу данных, можно переходить к тому, как её открывать. Из Project Window дважды щёлкните по TaskMenu.mnu, и включите элемент меню &Open\tF8. Добавьте фрагмент

class predicates openDB:(string FNAME).

predicates

onFileOpen : window::menuItemListener. clauses

onFileOpen(_Source, _MenuTag) :

FName= vpiCommonDialogs::getFileName("*.*", ["Email", "*.dbs"],

"Open", [], ".", _X), !,

openDB(FName). onFileOpen(_, _).

openDB(Fname) : file::existfile(Fname), !, emails::data_base_open(Fname).

openDB(_).

к ProjWin/TaskWindow.win/Code Expert/Menu/TaskMenu/id_file/id_file_open.

Постройте приложение. Затем из панели задач VDE, выберите пункт File/New in New Package, и вставьте нижеприведённую форму в корень проекта.

137

На этой форме есть list box – раскрывающийся список. Следовательно, необходимо добавить в него список вариантов. Чтобы сделать это, добавьте фрагмент

clauses

onListEditShow(_Source, _CreationData) : WlboxHandle=listEdit_ctl:tryGetVpiWindow(), !, vpi::lboxAdd(WlboxHandle, ["Person", "Commerce"]).

onListEditShow(_, _).

к ShowListener списка listEdit:listEdit_ctl.

Последний шаг в этом проекте – добавить код к кнопкам Save и List. Поэтому, добавьте

onSaveClick(_Source) = button::defaultAction() : Key= key_ctl:getText(),

Email= address_ctl:getText(), T= listEdit_ctl:getText(),

emails::insert_info(Key, Email, T).

к ClickResponder кнопки button:save_ctl. Наконец, добавьте

clauses

onListClick(_Source) = button::defaultAction() : emails::db_list().

к ClickResponder кнопки button:list_ctl. Потройте приложение ещё раз, и на этом всё. с этим .

21.2.Класс emails

%Файл emails.cl class emails

open core predicates

classInfo : core::classInfo. data_base_create:(string FileName) procedure (i). data_base_open:(string FileName). data_base_close:().

insert_info:(string Name, string Contact, string Tipo). data_base_search:(string Name, string Contact, string Tipo)

procedure (i, o, o). db_list:(). del:(string).

end class emails

%Файл emails.pro

implement emails open core

domains

contact= email(string, string, string). class facts dbState

dbase:(chainDB, chainDB::bt_selector) determ.

clauses

classInfo("db/emails/emails", "1.0"). data_base_create(FileName) :

138

ChainDB= chainDB::db_create(FileName, chainDB::in_file), ChainDB:bt_create("email", ContactName, 40, 21, 1), ChainDB:bt_close(ContactName),

ChainDB:db_close(), !. data_base_create(_).

data_base_open(FileName) :

ChainDB= chainDB::db_open(FileName, chainDB::in_file), ChainDB:bt_open("email", ContactName), assert(dbase(ChainDB, ContactName)).

data_base_close() : retract(dbase(ChainDB, BTREE)), !, ChainDB:bt_close(BTREE), ChainDB:db_close().

data_base_close().

insert_info(Key, Email, Tipo) : dbase(ChainDB, BTREE), !, ChainDB:chain_insertz("Contacts",

email(Key, Email, Tipo), RefNumber),

ChainDB:key_insert(BTREE, Key, RefNumber). insert_info( _, _, _).

data_base_search(Name, Contact, Tipo) : dbase(ChainDB, BTREE), ChainDB:key_search(BTREE, Name, Ref), ChainDB:ref_term(Ref, Term),

Term= email(Name, Contact, Tipo), !. data_base_search(_Name, "none", "none").

class predicates

getInfo:(chainDB, chainDB::ref, string, string) procedure (i, i, o, o).

dbLoop:(). clauses

getInfo(ChainDB, Ref, Name, Email) : ChainDB:ref_term(Ref, Term), Term= email(Name, Email, _), !.

getInfo(_, _, "none", "none").

db_list() : dbase(ChainDB, BTREE),

ChainDB:key_first(BTREE, Ref), !, getInfo(ChainDB, Ref, Name, Email), stdio::write(Name, ", ", Email), stdio::nl, dbLoop().

db_list() : stdio::write("None"), stdio::nl.

dbLoop() : dbase(ChainDB, BTREE), ChainDB:key_next(BTREE, Ref), !, getInfo(ChainDB, Ref, Name, Email), stdio::write(Name, ", ", Email), stdio::nl,

139

dbLoop(). dbLoop().

del(Key) :

dbase(ChainDB, BTREE), ChainDB:key_search(BTREE, Key, Ref), !, ChainDB:key_delete(BTREE, Key, Ref), ChainDB:term_delete("Contacts", Ref).

del(_Key).

end implement emails

21.3.Менеджер базданных

Функция, которая создаёт базу данных, очень проста:

data_base_create(FileName) :

ChainDB= chainDB::db_create(FileName, chainDB::in_file), ChainDB:bt_create("email", ContactName, 40, 21, 1), ChainDB:bt_close(ContactName),

ChainDB:db_close(), !. data_base_create(_).

Метод db_create/2 делает именно то, что означает, т.е., он создаёт базу данных внутри File Name и возвращает объект, чтобы ей управлять. Объект сохранён в ChainDB. Из ChainDB вызывается bt_create для получения BTree; пакет BTree инициализирует переменную ContactName указателем на BTree. Наконец, закрывается и BTree, и база данных.

Открывается база данных с помощью предиката, который почти так же прост, как и тот, что ис пользовался для её создания.

data_base_open(FileName) :

ChainDB= chainDB::db_open(FileName, chainDB::in_file), ChainDB:bt_open("email", ContactName), assert(dbase(ChainDB, ContactName)).

data_base_close() : retract(dbase(ChainDB, BTREE)), !, ChainDB:bt_close(BTREE), ChainDB:db_close().

data_base_close().

Предикат db_open открывает базу данных, сохранённую в FileName, и возвращает объект, кото рый может быть использован для управления ею; объект сохраняется в ChainDB, который использу ется для открытия BTree email. И ChainDB, и указатель BTree сохраняются в факт предикате, для бу дущих ссылок.

assert(dbase(ChainDB, ContactName))

Когда пользователь закрывает приложение, предикат data_base_close отменяет менеджер баз данных и указатель BTree из базы данных фактов, и закрывает и BTree, и ChainDB. До этой точки, мы описывали предикаты, которые администрируют базу данных; теперь опишем метод, который мож но использовать для внесения сведений в базу данных.

insert_info(Key, Email, Tipo) : dbase(ChainDB, BTREE), !, ChainDB:chain_insertz("Contacts",

email(Key, Email, Tipo),

140

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]