Поиск…


Вступление

Многие современные системы Prolog постоянно развиваются и добавили новые возможности для решения классических недостатков языка. К сожалению, многие учебники Prolog и даже учебные курсы по-прежнему вводят только устаревший пролог. Эта тема призвана проиллюстрировать, как современный Пролог преодолел некоторые проблемы и довольно жестокий синтаксис, который появляется в более раннем Prolog и все еще может быть введен.

CLP (FD) для целочисленной арифметики

Традиционно Prolog выполнял арифметику с использованием операторов is и =:= . Однако несколько текущих прологов предлагают CLP (FD) (Constraint Logic Programming over Finite Domains) как более чистую альтернативу для целочисленной арифметики. CLP (FD) основан на сохранении ограничений, которые применяются к целочисленному значению, и их объединению в памяти.

CLP (FD) является расширением в большинстве прологов, которые его поддерживают, поэтому их необходимо загружать явно. После того, как он будет загружен, то #= синтаксис может занять место как is и =:= . Например, в SWI-Prolog:

?- X is 2+2.
X = 4.

?- use_module(library(clpfd)).
?- X #= 2+2.
X = 4.

В отличие is , #= способен решать простые уравнения и унифицировать в обоих направлениях:

?- 4 is 2+X.
ERROR: is/2: Arguments are not sufficiently instantiated

?- 4 #= 2+X.
X = 2.

CLP (FD) предоставляет свой собственный синтаксис генератора.

?- between(1,100,X).
X = 1;
X = 2;
X = 3...

?- X in 1..100.
X in 1..100.

Обратите внимание, что генератор фактически не запускается: сохраняется только ограничение диапазона, готовое к последующим связям с ним. Генератор может быть принудительно запущен (и ограничения грубой силы) с использованием предиката label :

?- X in 1..100, label([X]).
X = 1;
X = 2;
X = 3..

Использование CLP может позволить некоторое умное сокращение случаев грубой силы. Например, используя целочисленную арифметику в старом стиле:

?- trace.
?- between(1,10,X), Y is X+5, Y>10.
...
Exit: (8) 6 is 1+5 ? creep
Call: (8) 6 > 10 ? creep
...
X = 6, Y = 11; ...

Пролог по-прежнему проходит через значения 1-5, хотя математически доказывается из данных условий, что эти значения не могут быть полезны. Использование CLP (FD):

?- X in 1..10, Y #= X+5, Y #> 10.
X is 6..10,
X+5 #= Y,
Y is 11..15.

CLP (FD) немедленно выполняет математику и разрабатывает доступные диапазоны. Добавление label([Y]) приведет к тому, что X будет выполняться только через полезные значения 6..10. В этом примере игрушек это не увеличивает производительность, потому что с таким небольшим диапазоном, как 1-10, обработка алгебры занимает столько же, сколько цикл сделал бы; но когда обрабатывается больший диапазон чисел, это может значительно сократить время вычисления.

Поддержка CLP (FD) является переменной между Prologs. Признанная лучшая разработка CLP (FD) находится в SICStus Prolog, который является коммерческим и дорогим. SWI-Prolog и другие открытые прогоны часто имеют некоторую реализацию. Visual Prolog не включает CLP (FD) в своей стандартной библиотеке, хотя для него доступны библиотеки расширений.

Forall вместо отказоустойчивых циклов

Некоторые «классические» учебники Prolog по-прежнему используют запутанный и подверженный ошибкам синтаксис цикла, основанный на отказе, когда для создания обратной привязки используется команда fail чтобы применить цель к каждому значению генератора. Например, чтобы напечатать все числа до заданного предела:

fdl(X) :- between(1,X,Y), print(Y), fail.
fdl(_).

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

nicer(X) :- forall(between(1,X,Y), print(Y)).

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

Visual Prolog имеет специальный синтаксический сахар для этих циклов в сочетании с предикатами функций (см. Ниже):

vploop(X) :- foreach Y = std::fromTo(1,X) do
                 console::write(X)
             end foreach.

Хотя это выглядит как императив для цикла, она по- прежнему следует правилам Пролога: в частности, каждая итерация Еогеаспа является его собственной областью.

Предикаты в стиле функции

Традиционно в Prolog «функции» (с одним выходом и связанными входами) были написаны как обычные предикаты:

mangle(X,Y) :- Y is (X*5)+2.

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

multimangle(X,Y) :- mangle(X,A), mangle(A,B), mangle(B,Y).

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

% Define the new infix operator
:- op(900, xfy, <-).

% Define our function in terms of the infix operator - note the cut to avoid
% the choice falling through
R <- mangle(X) :- R is (X*5)+2, !.

% To make the new operator compatible with is..
R <- X :-
    compound(X),            % If the input is a compound/function
    X =.. [OP, X2, X3],     % Deconstruct it
    R2 <- X2,               % Recurse to evaluate the arguments
    R3 <- X3,
    Expr =.. [OP, R2, R3],  % Rebuild a compound with the evaluated arguments
    R is Expr,              % And send it to is
    !.
R <- X :- R is X, !.        % If it's not a compound, just use is directly

Теперь мы можем написать:

multimangle(X,Y) :- X <- mangle(mangle(mangle(Y))).

Однако некоторые современные Прологи идут дальше и предлагают специальный синтаксис для этого типа предиката. Например, в Visual Prolog:

mangle(X) = Y :- Y = ((X*5)+2).
multimangle(X,Y) :- Y = mangle(mangle(mangle(X))).

Обратите внимание, что предикат <- operator и предикат функционального стиля выше по-прежнему ведут себя как отношения - для них законно иметь точки выбора и выполнять множественную унификацию. В первом примере мы предотвращаем это с помощью разрезов. В Visual Prolog нормально использовать функциональный синтаксис для отношений, а точки выбора создаются обычным способом - например, цель X = (std::fromTo(1,10))*10 успешно X = (std::fromTo(1,10))*10 с привязками X = 10 , X = 20, X = 30, X = 40 и т.д.

Объявления потока / режима

При программировании в Prolog не всегда возможно или желательно создавать предикаты, которые объединяются для каждой возможной комбинации параметров. Например, предикат between(X,Y,Z) который выражает, что Z численно находится между X и Y. Он легко реализуется в тех случаях, когда все X, Y и Z связаны (либо Z находится между X и Y, либо это не) или где X и Y связаны и Z свободен (Z унифицирует все числа между X и Y или предикат терпит неудачу, если Y <X); но в других случаях, например, когда X и Z связаны, а Y свободен, потенциально существует бесконечное количество унификаций. Хотя это можно реализовать, этого обычно не будет.

Объявление потока или декларации режима позволяют явное описание поведения предикатов при вызове с различными комбинациями связанных параметров. В случае between декларацией будет:

%! between(+X,+Y,+Z) is semidet.
%! between(+X,+Y,-Z) is nondet. 

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

  • det если предикат всегда преуспевает без точки выбора. Например, add(+X,+Y,-Z) является det потому что добавление двух заданных чисел X и Y всегда будет иметь ровно один ответ.
  • semidet если предикат либо преуспевает, либо не работает, без выбора. Как и выше, between(+X,+Y,+Z) является semidet потому что Z либо находится между X и Y, либо нет.
  • multi если предикат всегда преуспевает, но может иметь точки выбора (но также может и не быть). Например, factor(+X,-Y) будет multi потому что число всегда имеет по крайней мере один фактор - сам, но может иметь больше.
  • nondet если предикат может преуспеть с точками выбора или сбой. Например, between(+X,+Y,-Z) nondet потому что может быть несколько возможных объединений Z для чисел между X и Y, или если Y <X, то между ними нет чисел, и предикат терпит неудачу.

Объявления потока / режима также могут быть объединены с маркировкой аргументов, чтобы уточнить, что означают термины, или с помощью ввода. Например, between(+From:Int, +To:Int, +Mid:Int) is semidet .

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

В Mercury декларации потока и режима (и типы) являются обязательными и проверяются компилятором. Используемый синтаксис приведен выше.

В Visual Prolog декларации потока и режима и типы также являются обязательными, а синтаксис отличается. Вышеуказанное заявление будет написано так:

between : (int From, int To, int Mid) determ (i,i,i) nondeterm (i,i,o).

Значение такое же, как и выше, но с различиями, которые:

  • Объявления потока / режима отделены от объявлений типов (поскольку предполагается, что поток / режим для одного предиката не будет меняться при перегрузке типа);
  • i и o используются для + и - и сопоставляются с параметрами, основанными на упорядочении;
  • Используемые термины различны. det становится procedure , semidet становится determ и nondet становится nondeterm ( multi еще multi ).


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow