Интерпретатор Пролога
Мы уже полностью познакомились с интерпретатором правил. Для показа
действительной реализации механизма вывода, мы покажем ее применение при
создании сердца интерпретатора Пролога.
Хотя это выходит далеко за пределы того, что требуется для интерпре-
тации обычных баз правил, мы надеемся, что сможем дать некоторые сообра-
жения по адаптации механизмов вывода. Кроме того, многие (если не все)
встроенные предикаты, которые могут вам потребоваться при самостоятельной
работе, могут непосредственно копироваться с предлагаемых.
Нам потребуется некоторый интерфейс с внешним миром, включая правила
ввода и вывода результатов, но это будет изменяться в зависимости от кон-
кретного применения интерпретатора. Однако, в случае создания механизма
вывода для интерпретатора Пролога связь с внешним миром выглядит очень
простой - она состоит в одной строчке с подсказкой.
Такой интерфейс традиционного Пролога весьма слаб, он непосредствен-
но лежит в сердце машины. Для разработок, ориентированных на конечного
пользователя, таких как оболочки экспертных систем, интерпретатор обычно
скрыт от глаз внутри программы, которая посылает ему сканнирумые и транс-
лируемые правила базы, которые пользователь хочет выполнить.
UNIFY_TERM
unify_term выглядит также как и раньше:
unify_term (ATerm, var(ID), Env):-
free(ID), free(ATerm),!,
createVar(ATerm, Env, ID).
unify_term(_, STerm,_):-
bound(STerm), STerm = var("_"),1.
unify_term(Term, var(ID), Env):-
bound(ID),!,
member(e(ID, Term1), Env), Term1=Term.
unify_term(int(I), int(I),_):-!,
unify_term(atom(A), atom(A),_):-!.
unify_term(str(S), str(S),_):-!.
unify_term(char(C), char(C), _):-!.
unify_term(list(H1,T1), list(H2,T2), Env):-!,
unify_term(H1, H2, Env), unify_term(T1, T2, Env).
unify_term(nill, nill,_):-!.
unify_term(cmp(ID, LI), cmp(ID, L2), Env):-!,
unify_term(L1< L2, Env).
Сейчас мы более внимательно рассмотрим первое предложение
unify_term. Оригинал этого предложения с различными шаблонами на поток
параметров предиката, используется в случаях, когда необходимо заложить
все в обширную базу данных.
Наверное, вы помните, что работа unify_term заключалась в унификации
списков действительных термов из статических в выполняемые правила. При
закладывании информации мы должны действовать другим способом. Например,
если вы сделаете так:
.., Name=banana, Fondness=150, assert(favourite(Name,Fondness))), ...
то, вероятно, ожидаете, что будет заложено favourite(fruit (banana,150)),
а не favourite(Name, Fondness)). Другими словами, нам надо преобразовать
из действительных термов в статические, и именно это делает другой пото-
ковый шаблон unify_term. Однако, возникает проблема, что делать со сво-
бодными переменными. Рассмотрим следующее:
..., assert(member(X, [X|_])), ...
Предположим, мы хотим заложить в базу такой факт:
member(X, [X|_]).
Но это не так просто сделать, как кажется. Запомните, что во время
выполнения Х является не именем "Х", а связывается со свободной перемен-
ной. Решением станет создание имен для свободных переменных в форме
_<number>.
Обратите внимание, что мы не могли помещать свободную переменную
просто в виде знака подчеркивания. Это вызвано тем, что второе предложе-
ние для unify_term
unify_term(_, STerm,_):- bound(STerm), STerm = var("_"),1.
не позволит ей оказывать какое-либо действие при дальнейшем использовании
заложенного терма. Чтобы unify_term мог делать различные связывания, имя
свободных переменных должно отличаться от "_". Рассмотрим строку:
..., Food=fruit(_,_), Snack=Food, Snack=fruit(banana,150), ...
Вы можете ожидать, что Food должна иметь значение fruit(banana,150),
несмотря на то, что она была определена как имеющая только анонимные пе-
ременные. На боле высоком уровне это будет результатом того, что Snack и
Food разделяющие переменные, а по этой причине их подтермы также должны
быть разделены.
Такое разделение переменных может быть предотвращено только в случае
передачи в базу данных путем создания отдельных внутренних имен для ано-
нимных переменных, которые позволяют полноправно ввести их в среду в слу-
чае использования терма. Порождение внутреннего имени порождается следую-
щим механизмом:
database - varno
determ current_var(INTEGER)
predicates
reset_vargenerator
createVar(TERM,ENV,STRING)
lookup_termid(TERM,ENV,STRING)
get_next_unused(ENV,INTEGER,INTEGER,STRING)
vid_exist(STRING,ENV)
clauses
reset_vargenerator:-
retractall(current_var(_)),
assert(current_var(0)).
createVar(TERM,ENV,ID):-
lookup_termid(TERM,ENV,ID),!.
createVar(TERM,ENV,NEWID):-
retract(current_var(NO)),
NO1=NO+1,
get_next_unused(ENV,NO1,NO2,NEWID),
member(e(NEWID,TERM),ENV),
assert(current_var(NO2)).
lookup_termid(_,ENV,_):-free(ENV),!,fail.
lookup_termid(TERM,[e(ID,TERM1)|_],ID):- eeq(TERM,TERM1),!.
lookup_termid(TERM,[_|ENV],ID):-
lookup_termid(TERM,ENV,ID).
get_next_unused(ENV,NO,NO,NEWID):-
str_int(ID,NO),concat("_",ID,NEWID),
not(vid_exist(NEWID,ENV)),!.
get_next_unused(ENV,NO1,NO3,ID):-
NO2=NO1+1,
get_next_unused(ENV,NO2,NO3,ID).
vid_exist(_,ENV):-free(ENV),!,fail.
vid_exist(VID,[e(VID,_)|_]):-!.
vid_exist(VID,[_|L]):-vid_exist(VID,L).
Смелее! Здесь нет ничего особенно запутанного, хотя и может таким
показаться.
- Задачей create Var является возвращение ID для ATerm, который
является свободной переменной - усли бы он ею не был, то create
Var не был бы вызван. Он выполняет свою задачу, пытаясь найти в
среде терм с помощью lookup_termid; если терм введен в среду,
то нам захочется получить имя, под которым он известен.
- Предикат eeq - это реализация проверки на истинность, извест-
ная из традиционного Пролога. Мы ее опишем позже.
- Если тармин не найден в среде, то create Var должнен создать
ему имя. Это делается с помощью вызова get_next_unused, который
просто создает имя в форме _<number>, удостоверившись, что в
среде такого имени еще нет.
UNIFY_BODY
unify_body использует большое количество обработчиков, не все из ко-
торых являются равно понятными. Они требуются для интерпретации полного
Пролога. Эти специальные разработчики также показывают, как Пролог раз-
личными способами отодвигается от своих теоретических предпосылок. Полная
версия unify_body выглядит так:
unify_body(cmp(",",[ATerm1,ATerm2]),Env,BTOP):-!,
unify_body(TERM1,Env,BTOP),unify_body(TERM2,Env,BTOP).
unify_body(atom("!"),_,BTOP):-!,cutbacktrack(BTOP).
unify_body(cmp(";",[TERM,_]),Env,BTOP):-
unify_body(TERM,Env,BTOP).
unify_body(cmp(";",[_,TERM]),Env,BTOP):-!,
unify_body(TERM,Env,BTOP).
unify_body(cmp("not",[TERM]),Env,_):-
getbacktrack(BTOP),unify_body(TERM,ENV,BTOP),!,fail.
unify_body(cmp("not",_),_,_):-!.
unify_body(cmp("call",[TERM]),ENV,_):-!,
getbacktrack(BTOP),unify_body(TERM,ENV,BTOP).
unify_body(cmp("assert",[TERM]),ENV,_):- !,
handle_assert('0',TERM,ENV).
unify_body(cmp("asserta",[TERM]),ENV,_):-!,
handle_assert('a',TERM,ENV).
unify_body(cmp("assertz",[TERM]),ENV,_):-!,
handle_assert('z',TERM,ENV).
unify_body(cmp(PID,TERML),ENV,_):-
unify_terml(CALL,TERML,ENV),trace_call(PID,CALL).
unify_body(var(ID),ENV,_):-!,
member(e(ID,TERM),ENV),bound(TERM),
TERM=cmp(PID,TERML), trace_call(PID,TERML).
unify_body(atom(PID),_,_):-
trace_call(PID,[]).
Такой порядок предложений будет оптимальным не для всех случаев. Для
некоторых баз правил более эффективным окажется иной порядок, не говоря
уже о том, что многие конкретные случаи могут оказаться просто ненужными.
Первое новое предложение обрабатывает отсечения. Как уже указывалось
раньше, отсечение - это конструкция совершенно не типичная для Пролога.
Нет способа создать ее с помощью интерпретатора правил, не используя не-
которые нетипичные для Пролога конструкции; т.е то, что в Турбо Прологе
делает динамическое отсечение (предикат cutbacktrack).
Реальное отсечение в Турбо Прологе использовать нельзя, потому что
он составляет часть докомпиляционного анализа исходного кода, и, следова-
тельно, в принципе не вызывает действия в процессе выполнения. Во всяком
случае, когда в выполняемой базе правил встречается отсечение, то уста-
навливается заранее найденный указатель backtrack.
Пока мы еще не выяснили, где это делается, но для обычных случаев
этот указатель находится в call, откуда бы не вызывалось предложение оп-
ределенного пользователем предиката. Предложение, обрабатывающее функтор
call, просто отвечает за вызов в стандартном Прологе, в то время как
предложения для включения термов обрабатывают включение термов. Они дела-
ют это с помощью вызова:
handle_assert(Poscode,TERM,ENV):-
unify_term(CALL,TERM,ENV),
reset_vargenerator,
unify_term(CALL,STERM,ENV),
assertclause(Poscode,STERM),
fail.
handle_assert(_,_,_).
handle_assert создает действительный терм, затем отмечает его как
статический терм, генерируя внутренние имена для свободных переменных.
assertclause - это предикат, обрабатывающий простые термы, не имею-
щие тела.
Следующее за ним fail является программистской хитростью. Он уничто-
жит поправки, внесенные в среду при создании внутренних имен и восстанав-
ливает требуемое состояние среды. Неиспользование этого способа приведет
к ошибке только в случае, когда внесение чего-нибудь, содержащего аноним-
ные переменные было сделано на уровне цели, например вот так:
..., assert(fruit(_,_)), ...
Распечатка среды укажет на наличие двух переменных, имеющих одинако-
вое имя _<number>, хотя они очевидно не появлялись в цели.
Почему assert обрабатывается из unify_body, а не из call? Теорети-
чески, это результат того, что assert изначально не является частью тео-
рии решений. Практическое объяснение заключается в том, что используемый
handle_assert unify_term просто не имеет доступа в среду через call.
Можно обрабатывать внесение и без среды, но тогда все свободные пе-
ременные, включая те, которые имеют собственные имена, должны вноситься с
внутренне сгенерированными именами. Это необходимо, потому что без среды
невозможно найти имя терма. Такой выбор сделан для многих разработок на
традиционном Прологе, но для забавы мы покажем реализацию, идущую нес-
колько дальше.
/*Демонстрируем, как внесение обрабатывается интерпретатором*/
goal: L=[_,_], assert((p(X, L):- member(X, L)))
L=[_,_]
1 solution
goal: list(p/2)
p(X, [_1,_2]):- member(X, [_1, _2]).
Yes
1 solution
Остальная часть предложений самоочевидна. Предложение для случая var
(ID) позволяет вам назначить вызываемому терму переменную, а затем выз-
вать эту переменную (некоторые интерпретаторы Пролога запрещают такие
действия). Например:
..., Pred = fruit(banana,150), Pred, ...
выполнит fruit (снабженный базой данных с нужными предложениями).
В этом способе не делается ничего такого, что бы не могло быть сде-
лано предложением call из unify_body, которое мы рассмотрели раньше.
На этом заканчивается версия unify_body, необходимая для интерпрета-
ции Пролога; традиционный Пролог значительно отошел от лежащей в его ос-
нове теории, что наиболее заметно на "плоских" глобальных базах данных и
на отсечениях.
CALL
Если бы мы захотели подробно рассмотреть все стандартные предикаты,
встроенные в call, то нам пришлось бы слишком углубиться. Их главной
целью, помимо разрешения вам взаимодействовать с традиционным Прологом,
является возможность дать вам источник идей, которые вы можете включать в
собственные механизмы вывода. Не обращайте на это слишком много внимания,
хотя как и для многих встроенных предикатов традиционного Пролога, здесь
приходиться иметь дело с побочными эффектами или преобразованиями между
представлениями данных, информационное содержание которых остается одним
и тем же.
Еще раз посмотрите на последнее предложение call
call(ID, ATermList):-
getbacktrack(Btop),
clause(cmp(ID, STermList1), Body),
free(Env),
unify_terml(ATermList, STermList1, Env),
unify_body(Body, Env, Btop).
Оно идентично тому, которое было приведено раньше в дискуссии об об-
щем механизме вывода, но сейчас мы обратим внимание на реализацию динами-
ческого отсечения.
В интерпретаторах этого типа не сложно отсечь возврат внутри предло-
жения определенного пользователем предиката. Проблема заключается в более
общей природе отсечения, означающей, что действие распространяется на
весь предикат, а не только на текущее выполняемое предложение.
Без динамического отсечения в этом предложении call возникнет следу-
ющая ошибка, которая проявится как ошибка в вызове unify_body в этом
предложении, и приседет в предикат базы данных clause и к возможному на-
хождению нового предложения для преликата. Однако, поместив указатель
возврата, содержащийся getbacktrack обратно в unify_body, отсечение вы-
полняемой базе правил установит указатель возврата интерпретатора в ука-
затель, предшествующий вызову clause. Это предотвращает дальнейшую интер-
претацию предиката, содержащего предложение с отсечением.
Другими словами, если вы потерпите неудачу при вызове в следующем
фрагменте:
p :- a, b, !, c.
p :- d, e.
который проявится как невозможность вызова call во фрагменте:
unify_body(cmp(PID, ATermList), Env,_):-
unify_terml(Call, ATermList), Env,_),
call(PID, Call).
который опять будет отражен как невозможность выполнения unify_body в
предложении call, то без динамического отсечения интерпретатор во всех
случаях опять обращается к предикату clause и возвращает исходный код для
предложения р.
Если вы посмотрите на код unify_body, то вы увидете, что предотвра-
тить такое решение нельзя. Однако, когда встречается отсечение, вызов
cutbacktrack в unify_body устанавливает указатель возврата выше вызова
clause, и интерпретаор не будет искать для р другие предложения.
Если вы просмотрите предложения для call, то заметите, что большинс-
тво традиционных предикатов Пролога выполняются тем или иным способом.
Многие из них интересны только при интерпретации Пролога.
Давайте начнем с рассмотрения одного из волшебных предикатов, изоб-
ражаемого =.. и по некоторым историческим причинам читаемого как "univ".
univ совершает преобразования между сложными объектами и списками, где
функтор сложной системы становится первым элементом списка. С univ можно
вести следующий диалог:
Goal: fruit(banana,150) =.. [fruit, banana,150]
yes
Goal: GOAL =.. [member, X,[1,2,3]], call(GOAL)
GOAL=member(1, [1,2,3]), X=1
GOAL=member(2, [1,2,3]), X=2
GOAL=member(3, [1,2,3]), X=3
3 solution
Непосвящяенным это покажется чистой магией, потому что списки и
сложные структуры обычно представляются полностью отличными друг от дру-
га. Однако, если совпадает их информационное содержание, то между ними
легко устанавливается соответствие с помощью вспомогательного предиката
list_terml:
call("=..",[cmp(ID, ATermList), list(atom(ID), LIST)]):-!,
list_terml(LIST, ATermList).
predicates
list_terml(term, termlist)
clauses
list_terml(nill,[]):- !.
list_terml(list(H, T), [H|TT]):-
list_terml(T, TT).
Что же делает list_terml? Он передвигает заголовок списка в заголо-
вок списка термов или наоборот, а затем повторяет работу до конца спис-
ков.
Вторым магическим предикатом, который мы использовали в методах зап-
роса, является строгое равенство (==):
call("==",[T1, T2]):- !,
eeq(T1, T2).
Этот предикат выполняется в том случае, когда его аргументы являются
идентичными термами или разделяемыми переменными. Две свободные перемен-
ные не равны, если они не являются разделяемыми переменными. Это противо-
речит унификации (=), при выполнении которой две свободные переменные де-
лаются разделяемыми.
/* иллюстрирует функцию истинного равенства */
Goal: free(X), free(Y), call("==",[X, Y])
нет решения
Goal: free(X), Y=X, call("==",[X, Y])
X=_, Y=_
1 решение
В действительности такой проблемы в Турбо Прологе не возникает. В
нем можно создать следующее:
predicates
eeq(TERM,TERM)
eeqterml(TERML,TERML)
clauses
eeq(T1,T2):-free(T1),free(T2),T1=int(0),T2=int(1),!,fail.
eeq(T1,T2):-free(T1),free(T2),!.
eeq(T1,T2):-free(T1),!,fail; free(T2),!,fail.
eeq(cmp(ID,TERML1),cmp(ID,TERML2)):-!,eeqterml(TERML1,TERML2).
eeq(list(H1,T1),list(H2,T2)):-!,eeq(H1,H2),eeq(T1,T2).
eeq(X,X).
eeqterml([],[]):-!.
eeqterml([H1|T1],[H2|T2]):-
eeq(H1,H2),eeqterml(T1,T2).
В основном данная задача заключается в проверке на равенство без вы-
зова какого-либо согласования термов в том случае, когда они не согласо-
ваны.
Первое предложение для eeq пытается привести первый терм к како-
му-либо произвольноу значению, в данном случае int(0).
- Если происходит последующая унификация второго терма, то тер-
мы не являются разделяемыми переменными (В противном случае
второй терм также будет установлен в int(0).). Затем неисполне-
ние cut уничтожает установку и eeq не выполняется.
- С другой стороны, если термы являются свободными разделяемыми
переменными, то не сможет выполниться попытка унификации Т2 и
int(1) (потому что Т2 будет иметь то же самое значение, что и
Т1, называемое int(0)). Разворот уничтожит установку и направит
выполнение ко второму предложению, где выполнится eeq.
Следующие предложения для eeq более или менее понятны, хотя важно
заметить, что свободные переменные никогда не могут проникнуть дальше
третьего предложения, если же это произойдет, то при дальнейшей оценке
предиката они будут установлены.
Предикат retract, также как assert, выходит за официально разрешен-
ные в Прологе границы действия, но, по сравнению с assert, вызывает мень-
ше сложностей:
call("retract",[ATerm]):- !, handle_retract(ATerm).
retract управляется с помощью handle_retract:
predicates
nondeterm handle_retract(TERM)
clauses
handle_retract(cmp(":-",[cmp(ID,TERML),BODY])):-
bound(ID),!,
clause(cmp(ID,STERML),SBODY),
free(ENV),
unify_terml(TERML,STERML,ENV),
unify_term(BODY,SBODY,ENV),
retractclause(cmp(ID,STERML),SBODY).
handle_retract(cmp(":-",[HEAD,BODY])):-free(HEAD),!,
clause(SHEAD,SBODY),
free(ENV),
unify_term(HEAD,SHEAD,ENV),
unify_term(BODY,SBODY,ENV),
retractclause(SHEAD,SBODY).
handle_retract(cmp(ID,TERML)):-
clause(cmp(ID,TERML1),atom(true)),
free(ENV),
unify_terml(TERML,TERML1,ENV),
retractclause(cmp(ID,TERML1),atom(true)).
Первое предложение для handle_assert является некоторой оптимизаци-
ей, дающей значительно более быстрое действие retract в случае, когда из-
вестно имя (ID) того предложения, от которого надо отказаться. Почти
всегда это является делом случая; редко имеет смысл отказываться от того,
что происходит на вершине базы данных.
Предложения для handle_retract на самом деле делают немногое. Вызовы
unify_term и unify_terml служат двойной цели в случае унификации:
1. Гарантируют соответствие термов предложения, находящегося в
базе данных, термам, определенным в вызове retract.
..., retract(fruit(banana,_)), ... %взять назад вход banana
2. Они объединяют переменные в вызове retract с соответствующи-
ми термами в предложении, от которого надо отказаться
..., retract(fruit(banana,Fondness)), ...
%взять назад и проверить вход banana
Следуя традиции этого приложения Fondness будет объеденен с целым
термом 150.
Благодаря многочисленным унификациям, возможно объединенным с не-вы-
полнить-и-возвратиться-и-взять-снова-другое-предложение, retract оказыва-
ется весьма медленным. (Не забывайте, что это те самые действия, которые
традиционный Пролог делает во всех ситуациях).
И, наконец, мы рассмотрим, вероятно самый запутанный предикат - пре-
дикат write:
call("write", ATermList):- !,
writeterml("write", ATermList).
Он использует ту же группу предикатов, что и display, который печа-
тает термы в префиксной форме (т.е. без учета объявлений операторов):
writeterml(_,[]):-!.
writeterml(DISPLAY,[H|T]):-
wterm(DISPLAY,H),
writeterml(DISPLAY,T).
wterm(_,TERM):-
free(TERM),!,write('_').
wterm(_,int(X)):-!,
write(X).
wterm("write",str(X)):-!,
write(X).
wterm(_,str(X)):-!,
write('"',X,'"').
wterm("write",char(X)):-!,
write(X).
wterm(_,char(X)):-!,
write('`',X).
wterm(_,atom(X)):-!,
write(X).
wterm(_,var(X)):-!,
write(X).
wterm(_,nill):-!,
write("[]").
wterm(DISPLAY,list(HEAD,TAIL)):-!,
write('['),
wlist(DISPLAY,list(HEAD,TAIL)),
write(']').
wterm(DISPLAY,cmp(FID,ATERML)):-
wcmp(DISPLAY,FID,ATERML).
wcmp(DISPLAY,FID,ATERML):-
DISPLAY><"display",
OP=FID,
op(PRIOR,ASSOC,OP),
wop(DISPLAY,PRIOR,ASSOC,OP,TERML),!.
wcmp(DISPLAY,FID,TERML):-
write(FID,'('),
wterml(DISPLAY,TERML),
write(')').
При печати термов могут возникнуть некоторые сложности, т.к. эта
процедура противоположна трансляции. Однако, трансляция - это механичес-
кий процесс, который мы уже поняли. То же относится к печати. Действия
wterm совершенно очевидны, и мы не будем волноваться из-за предиката, пе-
чатаемого списком, потому что его содержимое весьма понятно. wcmp приво-
дит к неприятностям, когда пытаются операторы; значительно проще действо-
вать, когда установлена опция display, т.к. дело касается только написа-
ния списка термов для каждого терма.
Предположим, что мы нашли оператор, вызвав предикат ор в первом
предложении wcmp, и переходим к вызову wop:
prefix(fx,x). prefix(fy,y).
suffix(xf,x). suffix(yf,y).
infix(xfx,x,x). infix(xfy,x,y). infix(yfx,y,x). infix(yfy,y,y).
wop(DISPLAY,PRIOR,ASSOC,OP,[ATERM]):-
prefix(ASSOC,XY),!,
write(OP),
wright(DISPLAY,XY,PRIOR,ATERM).
wop(DISPLAY,PRIOR,ASSOC,OP,[ATERM]):-
suffix(ASSOC,XY),!,
wleft(DISPLAY,XY,PRIOR,ATERM),
write(OP).
wop(DISPLAY,PRIOR,ASSOC,OP,[TERM1,TERM2]):-
infix(ASSOC,LEFT_XY,RIGHT_XY),
wleft(DISPLAY,LEFT_XY,PRIOR,TERM1),
write(OP),
wright(DISPLAY,RIGHT_XY,PRIOR,TERM2).
brackets_needed(_,PRIOR,ATERM):-
bound(TERM),TERM=cmp(FID,_),
OP=FID,
op(PRIOR1,_,OP),
PRIOR1>PRIOR,!.
brackets_needed(x,PRIOR,ATERM):-
bound(TERM),
ATERM=cmp(FID,_),
OP=FID,
op(PRIOR,_,OP),!.
wright(DISPLAY,XY,PRIOR,ATERM):-
brackets_needed(XY,PRIOR,ATERM),!,
write(" ("),
wterm(DISPLAY,ATERM),
write(')').
wright(DISPLAY,_,_,ATERM):-
write(' '),
wterm(DISPLAY,ATERM).
wleft(DISPLAY,XY,PRIOR,ATERM):-
brackets_needed(XY,PRIOR,ATERM),!,
write('('),
wterm(DISPLAY,ATERM),
write(") ").
wleft(DISPLAY,_,_,ATERM):-
wterm(DISPLAY,ATERM),
write(' ').
Нет, само по себе это работать не будет. wop определяет (с помощью
небольшой группы infix, prefix и postfix предложений), как будет осущест-
вляться управление оператором и его связывание с другими. Потом он вызы-
вает один из двух операторов:
- или wright для выписывания терма под управлением префиксного
оператора
- wleft - для выписывания терма под управлением постфиксного
оператора.
В случае бинарного оператора используются обе формы.
Связывание имеет большое значение в случае, когда несколько операто-
ров, имеющих одинаковое старшинство, появляются друг за другом. Будем
считать, что знак "^" обозначает степень, тогда выражение 2^3^4 получит
значение 2^(3^4), потому что "^" - оператор, начинающий связывание спра-
ва. Это выглядит несколько необычно, ведь большинство операторов выполня-
ются слева направо. Рассмтрим деление: 2/3/4 означает (2/3)/4, а не
2/(3/4).
Дело становится совсем плохим, когда операторы вне зависимости от
наличия скобок разрешают иметь вокруг себя только операторы меньшего
старшинства. Такая ситуация возникает крайне редко, но если вы скомпили-
руете и запустите интерпретатор, то попытайтесь выполнить что-либо типа:
X='='('=(1,2),'='(1,2))
В префиксной форме это совпадает с:
X=((1=2)=(1=2))
(Никакого смысла в этом выражении не предполагается). Вы заметите,
что ответ интерпретатора включает скобки, хотя они и отсутствовали в уни-
фикации, использующей префиксную нотацию. Это вызвано тем, что объявление
оператора "=" определяет его как xfx, что означает, что он не будет при-
нимать расположенные вокруг него операторы того же или большего уровня
старшинства, если же они присутствуют, то должны быть заключены в скобки.
Второе предложение brackets_needed, проверяющее на ввод аргумента x, уп-
равляет этой ситуацией, в то время как первое предложение brackets_needed
за обычнми случаями старшинства операторов, требующими скобок, например
(1+2)*3.
Вот последнее замечание о wcmp. В предложении:
wcmp(DISPLAY,FID,ATERMList):-
DISPLAY><"display",
OP=FID,
op(PRIOR,ASSOC,OP),
wop(DISPLAY,PRIOR,ASSOC,OP,ATERMList),!.
соблазнительно поместить отсечение сразу после вызова ор. Ведь после вы-
яснения того, что идентификатор функтора (Functor IDentifir) явлется опе-
ратором (OPerator), других выборов не остается, не так ли? Нет, не так.
Оператор может одновременно иметь и унарное и бинарное объявление, как
это происходит в случае минуса, например:
op(500, yfx, "-"). op(500, fx, "-"). op(400, yfx, "*").
В этом случае, как и в других случаях бинарности, wop не выполнится
и вернется назад для проверки, не совпадает ли первое объявление, возвра-
щаемое ор, со списком термов вопроса. При выполнении должна сохраняться
возможность возвращения в предикат ор с попыткой найти другое предложе-
ние.
На этом заканчивается наш обзор встроенных предикатов интерпретато-
ра. Если вам будет сопутствовать удача, то предикаты, которые могут вам
потребоваться в своих разработках, окажутся более или менее сходными с
ними. Особенно это относится к функции eval, для дерева выражений, ранее
описанной в данном приложении, и к предикату печати термов.
Возможно, вы захотите, чтобы предикат печати термов возвращал стро-
ку, а не отображал вывод на экране или в файле. Это несложно реализовать.
Вам потребуется только добавить еще один аргумент для строки и использо-
вать concat или format для ассемблирования предполагаемой строки. Расс-
мотрим следующий случайный пример:
wleft(DISPLAY,XY,PRIOR,ATERM):-
brackets_needed(XY,PRIOR,ATERM),!,
write('('),
wterm(DISPLAY,ATERM),
write(") ").
wleft(DISPLAY,_,_,ATERM):-
wterm(DISPLAY,ATERM),
write(' ').
Считая, что wterm и другие предикаты были изменены подобным образом,
этот случайный пример можно преобразовать в:
wleft(DISPLAY,XY,PRIOR,ATERM, OS):-
brackets_needed(XY,PRIOR,ATERM),!,
wterm(DISPLAY,ATERM, S1),
format(OS,"(%) ", S1).
wleft(DISPLAY,_,_,ATERM, OS):-
wterm(DISPLAY,ATERM,S1),
concat(S1," ", OS).
- Справочное руководство по турбо прологу 2.0
- Глава 2. Элементы языка 50
- Глава 3. Интерфейс с другими языками 75
- Часть 2. Стандартные предикаты 91
- Часть 3. Приложения 250
- Введение
- Дистрибутивные диски
- Рекомендуемая литература:
- Часть 1. Руководство программиста по турбо прологу глава 1. Система меню турбо пролога
- Пользователям Турбо Пролога 1.X
- Структура меню Турбо Пролога
- Выбор элементов меню.
- "Горячие" клавиши.
- Системные окна и строки помощи
- Окно редактирования.
- Как войти в окно редактирования
- Компоненты окна редактирования
- Как выйти из окна редактирования.
- Окно трассировки
- Окно сообщений
- Диалоговое окно
- Строка помощи
- Главное меню.
- Окно редактирования.
- Окно вспомогательного редактирования (Xedit).
- Окно просмотра.
- Изменение размера окон.
- Просмотр окон
- Создание нового файла.
- Загрузка существующего файла.
- Команды редактора.
- Сохранение исходного текста в файле.
- Запись исходного текста на диск
- Вспомогательный редактор
- Меню и команды
- Меню файлов (Files)
- Загрузка файлов (Load)
- "Указка" (Pick)
- Новый (New)
- Выход (Quit)
- Команда редактирования (Edit)
- Команда запуска (Run)
- Меню компиляции (Compile)
- Память (Memory)
- Меню опций (Options)
- Опции компоновки (Link Options)
- Стек (Stack)
- Область ссылок (Trail Array)
- "Куча" (Heap)
- Контроль во время исполнения (Run-time check)
- Цвета (Colors)
- Размер окна (Window size)
- Каталоги
- Текущий каталог (Current directory)
- Авто-загрузка сообщений (Avto-Load Message)
- Режим экрана (Screen mode)
- Конфигурация клавиатуры (Keyboard Configuration)
- Перемещение курсора (Cursor movement)
- Глава 2. Элементы языка
- Ключевые слова.
- Специальные предикаты.
- Разделы программы.
- Раздел доменов (domains).
- Стандартные домены.
- Списковые домены.
- Домены составных объектов.
- Файловый домен.
- Специально заданные предопределенные домены.
- Сокращение объявлений доменов.
- Объявление ссылочных доменов.
- Раздел предикатов (predicates).
- Раздел базы данных (database).
- Раздел предложений (clauses).
- Простые константы.
- Переменные.
- Составные объекты.
- Списки - Специальный вид составных объектов.
- Раздел констант.
- Условная компиляция.
- Включение файлов в вашу программу.
- Директивы компилятора.
- Управление памятью в Турбо Прологе.
- Экономия ресурсов памяти.
- Управление распределением памяти.
- Генерирование выполняемых программ.
- Модульное программирование.
- Проекты.
- Глобальные объявления.
- Глобальные домены.
- Глобальная база данных.
- Глобальные предикаты.
- Компиляция и компоновка модулей.
- Пример.
- Глава 3. Интерфейс с другими языками
- Вызов других языков из Турбо Пролога.
- Что делать перед вызовом.
- Объявление внешних предикатов.
- Вызов соглашений и передача параметров.
- Соглашение об обозначениях
- Использование ключевого слова as
- Вызов процедуры, написанной на Си из Пролога.
- Опции компилятора Турбо Си и компоновка.
- Вызов программ на языке ассемблера из Турбо Пролога.
- Использование внутреннего ассемблера Турбо Си.
- Вызов Турбо Пролога из других языков.
- Динамическое распределение памяти.
- Передача составных объектов в другие языки.
- Передача списков.
- Передача структур.
- Примеры.
- Обработка списков.
- Программирование на низком уровне.
- Часть 2. Стандартные предикаты
- Arc/5 Графика
- Bar/4 Графика
- Bt_close/2 Внешняя База Данных
- Bt_create/5 Внешняя База Данных
- Bt_delete/2 Внешняя База Данных
- Bt_open/3 Внешняя База Данных
- Bt_statistics/8 Внешняя База Данных
- Chain_delete/2 Внешняя База Данных
- Chain_first/3 Внешняя База Данных
- Chain_inserta/5 Внешняя База Данных
- Chain_insertafter/5 Внешняя База Данных
- Chain_insertz/5 Внешняя База Данных
- Chain_last/3 Внешняя База Данных
- Chain_next/3 Внешняя База Данных
- Chain_prev/3 Внешняя База Данных
- Chain_terms/5 Внешняя База Данных
- Circle/3 Графика
- Cleardevice/0 Графика
- Clearviewport/0 Графика
- Closegraph/0 Графика
- Db_btrees/2 Внешняя База Данных
- Db_chains/2 Внешняя База Данных
- Db_close/1 Внешняя База Данных
- Db_create/3 Внешняя База Данных
- Db_delete/2 Внешняя База Данных
- Db_flush/1 Внешняя База Данных
- Db_garbagecollect/1 Внешняя База Данных
- Db_open/3 Внешняя База Данных
- Db_openinvalid/3 Внешняя База Данных
- Db_statistics/5 Внешняя База Данных
- Detectgraph/2 Графика
- Drawpoly/1 Графика
- Ellipse/6 Графика
- Fillellipse/4 Графика
- Fillpoly/1 Графика
- Floodfill/3 Графика
- Getarccoords/6 Графика
- Getaspectratio/2 Графика
- Getbkcolor/1 Графика
- Getcolor/1 Графика
- Getdefaultpalette/1 Графика
- Getdrivername/1 Графика
- Getfillpattern/1 Графика
- Getfillsettings/2 Графика
- Getgraphmode/1 Графика
- Getimage/6 Графика
- Getlinesettings/3 Графика
- Getmaxcolor/1 Графика
- Getmaxx/1 Графика
- Getmaxy/1 Графика
- Getmaxmode/1 Графика
- Getmodename/2 Графика
- Getmoderange/3 Графика
- Getpalette/1 Графика
- Getpalettesize/1 Графика
- Getpixel/3 Графика
- Gettextsettings/5 Графика
- Getviewsettings/5 Графика
- Getx/1 Графика
- Gety/1 Графика
- Graphdefaults/0 Графика
- Graphresult/1 Графика
- Imagesize/5 Графика
- Initgraph/5 Графика
- Key_current/4 Внешняя База Данных
- Key_delete/4 Внешняя База Данных
- Key_first/3 Внешняя База Данных
- Key_insert/4 Внешняя База Данных
- Key_last/3 Внешняя База Данных
- Key_next/3 Внешняя База Данных
- Key_prev/3 Внешняя База Данных
- Key_search/4 Внешняя База Данных
- Line/4 Графика
- Linerel/2 Графика
- Lineto/2 Графика
- Moverel/2 Графика
- Moveto/2 Графика
- Outtext/1 Графика
- Outtextxy/3 Графика
- Pieslice/5 Графика
- Pieslicexy/6 Графика
- Putimage/4 Графика
- Putpixel/3 Графика
- Rectangle/4 Графика
- Restorecrtmode/0 Графика
- Setactivepage/1 Графика
- Setallpalette/1 Графика
- Setaspectratio/2 Графика
- Setbkcolor/1 Графика
- Setcolor/1 Графика
- Setfillpattern/2 Графика
- Setfillstyle/2 Графика
- Setgraphmode/1 Графика
- Setgraphbufsize/1 Графика
- Setlinestyle/3 Графика
- Setpalette/2 Графика
- Setrgbcolor/4 Графика
- Settextjustify/2 Графика
- Settextstyle/3 Графика
- Setusercharsize/4 Графика
- Setviewport/5 Графика
- Setvisualpage/1 Графика
- Setwritemode/1 Графика
- Term_delete/3 Внешняя База Данных
- Term_replace/4 Внешняя База Данных
- Textheight/2 Графика
- Textwidth/2 Графика
- Часть 3. Приложения приложение а. Введение вdos
- Что такое dos
- Как загрузить программу?
- Каталоги
- Подкаталоги
- Avtoexec.Bat-файл
- Команда path
- Смена каталогов
- Приложениеb. Интерактивный редактор турбо пролога
- Быстрый вход, быстрый выход
- Служебная строка окна редактора
- Основные команды редактора
- Команды Редактора, совпадающие с командами WordStar Основные команды работы с курсором
- Команды быстрого движения курсора
- Команды вставки и удаления
- Команды работы с блоками
- Остальные команды редактора
- Старые команды
- Поиск и замена
- Редактор Турбо Пролога по сравнению с WordStar
- Корректировка файла на диске.
- Приложение c. Сообщения об ошибках
- Приложение d. Различия между версиями 1.1 и 2.0
- Файл определения проекта
- Новые ограничения по именам
- Новые предопределенные домены
- Контроль состава параметров шаблона (flow pattern)
- Поддержка старых командных клавиш редактора
- Ошибочные ситуации в стандартных предикатах
- Расширение Турбо Пролога 2.0
- Системные расширения
- Приложениеe. Словарь специальных терминов
- Приложение f. Географическая база данных (Geobase)
- Использование Geobase
- Системы с 2-мя флоппи-дисковыми устройствами
- Компиляция Geobase
- Создание автономной программы
- Главное меню Geobase
- Справка
- Команды dos
- Редактор
- Запросы к базе данных
- Просмотр языка
- Корректировка языка
- Принципы Geobase
- Использование принципа Geobase
- Создание вашей базы данных
- Преобразование Geobase
- Предикат "ent"
- Предикат "db"
- Трансляция запросов на естественном языке
- Внутренние имена объектов
- Внутренние имена связей
- Определение структуры предложений
- База данных языка
- Грамматический разбор с помощью разделения списков
- Заключение
- Приложение g. Анализатор предложений на турбо прологе
- Использование анализатора предложений
- Помощь пользователя
- Операционная система
- Редактирование базы данных
- Загрузка базы данных из файла
- Сохранение базы данных
- Анализ предложений
- «Показать/изменить словарь»
- Приложениеh.Geni: оболочка экспертной системы
- Использование geni
- Запросы geni
- Просмотр знаний
- Корректировка базы знаний
- Сохранение базы знаний
- Создание новой базы знаний
- Чистка текущей базы знаний
- Проектирование новой базы знаний
- Приложение I. Tlib: библиотека турбо
- Компоненты командной строки tlib
- Компонент «описание»
- Список операций
- Имена файла и модуля
- Операции tlib
- Создание библиотеки
- Использование файла ответа
- Расширенные возможности: опция /с
- Примеры
- Приложение j. Tlink: компоновщик турбо
- Активизация tlink
- Использование ответных файлов
- Компоновка модулей Турбо Пролога и Турбо Си.
- Модуль инициализации
- Библиотеки
- Опции tlink
- Опция /c
- Опция /d
- Опция /I
- Опция /l
- Опции /m, /s, /X
- Опция /m
- Опция /s
- Опция /X
- Опция /n
- Ограничения
- Сообщения об ошибках
- Предупреждения
- Нефатальные ошибки
- Фатальные ошибки
- Приложение k. Метапрограммирование Введение
- Пролог/Турбо: развитие и философские установки
- Конструкторские решения в Турбо Прологе
- Чем Турбо зарядило Турбо Пролог
- Интерпретатор правил с обратной цепочкой рассуждений
- Термы (горючее для интерпретатора правил)
- Статические термы: область sTerm (статических термов)
- Интерпретирование области термов.
- Что такое интерпретатор?
- Интерпретация правил в Прологе
- Использование sTerm и aTerm для моделирования интерпретатора правил
- Переменные и среда
- Процесс унификации
- Унификация статических и действительных термов
- Интерпретация тела правила
- Выполнение выполняемых термов: предикат call.
- Встроенные предикаты
- Сканнер и транслятор
- Операторы традиционного Пролога
- Использование операторов в Турбо Прологе
- Интерпретатор Пролога
- Реализация трассировки
- Теперь объединим все вместе
- Расширения экспертной системы
- Цепочка с прямым порядком рассуждений
- Стандартные предикаты, реализованные в pie
- Заключение