Стили и методы программирования

         

Динамическое пополнение и порождение программы


Поскольку структура программы и структура Поля зрения практически изоморфны, естественно ставить вопрос о динамическом порождении PROLOG-программ. Кроме этого высокоуровневого соображения, есть и прагматическое, которое можно извлечь из нашей программы 6.3.2. В нашу программу мы были вынуждены записать и определение лабиринта.Конечно же, можно было бы прочитать с помощью встроенной функции read определение лабиринта и записать его в список, но тогда мы почти утратили бы все преимущества языка PROLOG, не избавившись при этом ни от одного его недостатка. Гораздо естественнее иметь возможность прочитать базу данных из файла.

Для этой цели в PROLOG был введен встроенный предикат consult(file[.pl]). Он читает предложения и факты из файла и помещает их в конец программы, тем самым оставляя в неприкосновенности ранее данные определения предикатов. С его использованием наша программа может быть переписана в следующем виде.

way0(X,Y,Z):-consult(labyr),way(X,Y,Z). way(X,X,[X]). way(X,Y,[Y|Z]):-connect(U,Y), not member(Y,Z),way(X,U,Z). way(X,Y,[Y|Z]):-connect(U,Y), way(X,U,Z).

Листинг 6.4.1. Вводимый лабиринт

Пример файла labyr.pl:

connect(begin,1). connect(1,begin). connect(1,2). connect(2,3). connect(3,1). connect(3,4). connect(4,end).

Программа 6.4.1 представляет лишь идею решения, но эту идею она представляет исключительно выразительно. PROLOG приспособлен для нахождения решения, но не для его оптимизации. Например, если стремиться найти в некотором отношении оптимальный путь, то предыдущая программа окажется затемнена частностями языка PROLOG, которые испортят выразительность, и тем не менее не дадут возможность решить задачу столь же эффективно, как это делается на традиционном языке.

Есть еще один класс встроенных отношений, которые действуют как встроенные функции, но не требуют явной активизации операцией is. К ним, в частности, относятся многие действия над списками. Рассмотрим, например, предикат append(E1,E2,E3). Он корректно унифицируется, когда объединение первых двух списков является третьим.
Соответственно, он может использоваться для вычисления любого из своих трех аргументов, если два других заданы. Например,

append(X,Y,Z)

при Z=[a,b,c,d], Y=[c,d] унифицируется как X=[a,b].

Очень жаль, что в PROLOG таким же образом не реализованы арифметические функции!

Для динамического порождения фактов и предложений имеются функции, разбирающие предложения и синтезирующие их. Функторы могут быть объявлены метапредикатами, и тогда некоторые из их аргументов могут быть предложениями. Из метапредикатов важнее всего два, перечисленных ниже.

Метапредикат assert(P:-P1,. . . ,Pn) помещает свой аргумент в PROLOG- программу. Имеются несколько его вариантов, располагающие новое предложение или факт в начало или в конец программы. Метапредикат retract(P:-P1,. . . ,Pn), наоборот, удаляет из программы предложение или факт, унифицируемый с его аргументом.

С их помощью можно, в частности, имитировать различные более эффективные алгоритмы перебора для работы с лабиринтом, но при этом программа до некоторой степени теряет ясность структуры и становится крайне трудно ее отладить и модифицировать. А эффективности, сравнимой с традиционными методами, достичь все равно не удастся.

Но, например, если Вы анализируете сложную систему правил и ищете вывод, то результат анализа часто можно записать как файл динамически порожденных предложений, и это, наоборот, делает программу красивее, а ее отладку легче. Так что есть смысл использовать динамическое порождение в том случае, когда программа сначала коллекционирует и анализирует информацию, а лишь затем начинает действовать. А порождение, перемешанное с действиями, — кратчайший путь к провалу программы, и должно рассматриваться как хакерство.

Далее, после того, как использованы динамически порожденные факты или предложения, их можно удалить из программы при помощи предиката

retractall(Name / Arity)

Этот предикат удаляет все предложения и факты, говорящие о предикате Name арности Arity. Естественно, при этом удаляется лишь определение. Если Вы его использовали, то нужно потрудиться удалить также использующие предложения.


Поэтому предикаты лучше удалять целыми содержательно связанными группами, и концентрировать все такие удаления в одном предложении для каждой группы.

Динамическое порождение программ — метод, исключительно сильный, когда применен в подходящей обстановке и корректно, и исключительно опасный, когда применен в неподходящем окружении либо хоть чуть-чуть некорректно. В традиционных языках ему всячески препятствуют. Там он (по причине очень многих практических провалов) имеет одиозную репутацию. Но эта репутация связана прежде всего с тем, что такой рискованный и красивый метод требует высокой проработки идей, а в традиционном программировании мы, пытаясь его применить, вынуждены прорабатывать глупейшие технические проблемы (строить множество подпорок, см. словарь).

Внимание!

В новых версиях языка PROLOG предикаты, которые Вы намерены динамически видоизменять, нужно объявить. Например, dynamic(connect).

Для проверки типов термов имеются, в частности, следующие встроенные предикаты.

  • var(Term). Унифицируется, если Term свободная переменная.
  • nonvar(Term). Унифицируется, если textsfTerm не свободная переменная.
  • integer(Term) Успешен, если Term является целым числом (именно числом, а не выражением).
  • float(Term) Успешен, если Term является действительным числом.
  • number(Term) Успешен, если Term является числом.
  • atom(Term) Успешен, если Term является атомом.
  • string(Term). Успешен, если Term является строкой.
  • atomic(Term). Успешен, если Term является неделимым значением (число, строка или атом).
  • compound(Term). Успешен, если Term является сложным выражением.
  • ground(Term). Успешен, если Term не содержит свободных переменных.


Для анализа и построения термов имеются, в частности, следующие предикаты.

  • functor(Term, Functor, Arity) Унифицируется, если Term является термом с главным функтором Functor арности Arity. Term, являющийся переменной, унифицируется с новой переменной. Если Term является атомом либо числом, то его арностью считается 0, а функтором он сам.
  • arg(Arg, Term, Value) Выделение аргумента терма Term по его номеру Arg.Номера начинаются с 1.Естественно, что данный предикат может быть использован и для определения номера аргумента в терме.
  • Term =.. List Унифицируется, если List является списком, головой которого является функтор терма Term, а оставшиеся члены задают аргументы (сравните с тем, что ниже рассматривается в языке LISP!) Если не использовать его как операцию, то имя этого предиката Univ. Естественно, он может работать в обе стороны, разбирая либо собирая терм.


Примеры.

?- send(hello, X) =.. List. List = [send, hello, X] ?- Term=.. [send, hello, X] Term = send(hello,X)

free_variables(Term, List) List унифицируется как список новых переменных, каждая из которых равна свободной терма Term.

atom_codes(Atom, String) Преобразование атома в строку и наоборот.

Многие из реализаций языка PROLOG включают пакет прогонки, позволяющий осуществлять частичное вычисление PROLOG-программы.


Содержание раздела