При чтении SICP я столкнулся с главой 4.4 логического программирования. Затем я начал изучать язык программирования Пролога и попытался понять некоторые простые присвоения в Прологе. Я нашел, что Пролог, кажется, испытывает затруднения из-за числовых вычислений.
Вот вычисление факториала в стандартном Прологе:
f(0, 1).
f(A, B) :- A > 0, C is A-1, f(C, D), B is A*D.
Проблемы, которые я нахожу, - то, что я должен представить две вспомогательных переменные (C
и D
), новый синтаксис (is
) и что проблема необратима (т.е. f(5,X)
работы как ожидалось, но f(X,120)
не делает).
Наивно, я ожидаю это по крайней мере C is A-1, f(C, D)
выше может быть заменен f(A-1,D)
, но даже который не работает.
Мой вопрос: Почему я должен сделать этот дополнительный "материал" в числовых вычислениях, но не в других запросах?
Я действительно понимаю (и SICP вполне соглашается с этим), что в общей информации о "то, что сделать", недостаточно для ответа на вопрос, "как сделать это". Таким образом, декларативное знание в (по крайней мере немного) математические проблемы недостаточно для фактического решения этих проблем. Но это вызывает следующий вопрос: Как делает этот дополнительный "материал" в Прологе, помогают мне ограничить формулировку просто теми проблемами, где "то, что сделать", достаточно для ответа, "как сделать это"?
Забудьте о переменных и подумайте, что A
и B
- это просто название значения, которое можно поместить в этот раздел (X: - Y).
, чтобы сделать его доступным. Подумайте о X = (2 + (3 * 4))
как о структурах данных, которые представляют математическое выражение. Если вы попросите пролог достичь цели f (A-1, B)
, он попытается найти такой атом f (A-1, B).
или правило (f (A-1, B): - Z), Z.
, которое будет объединено для «успеха».
is / 2
пытается объединить первый аргумент с результатом интерпретации второго аргумента как выражения. Рассмотрим eval / 2
как вариант is / 2
:
eval(0, 1-1). eval(0, 2-2). eval(1,2-1).
eval(Y, X-0):- eval(Y, X).
eval(Y, A+B):- eval(ValA, A), eval(ValB, B), eval(Y, ValA + ValB).
eval(4, 2*2).
eval(0, 0*_). eval(0, _*0).
eval(Y, X*1):- eval(Y, X).
eval(Y, 1*X):- eval(Y, X).
eval(Y, A*B):- eval(ValA, A), eval(ValB, B), eval(Y, ValA * ValB).
Причина, по которой f (X, 120)
не работает, проста > / 2
работает только тогда, когда его аргументы связаны (т.е. вы не можете сравнивать что-то еще не определенное, например X
, с чем-либо еще). Чтобы исправить это, вы должны разделить это правило на:
f(A,B) :- nonvar(A), A > 0, C is A-1, f(C, D), B is A*D.
f(A,B) :- nonvar(B), f_rev(A, B, 1, 1).
% f_rev/4 - only first argument is unbound.
f_rev(A, B, A, B). % solution
f_rev(A, B, N, C):- C < B, NextN is (N+1), NextC is (C*NextN), f_rev(A, B, NextN, NextC).
Обновление : (исправлено f_rev / 4
)
Возможно, вас заинтересует решатель конечной области. Был вопрос об использовании таких вещей. Используя #> / 2
и # = / 2
, вы можете описать некоторые формулы и ограничения, а затем разрешить их. Но эти предикаты используют особые возможности некоторых систем прологов, которые позволяют связывать имя с некоторыми атрибутами, что может помочь сузить набор возможных значений путем пересечения ограничения. Некоторые другие системы (обычно такие же) позволяют изменять порядок целей обработки («приостановить»).
Также member (X, [1,2,3,4,5,6,7]), f (X, 120)
, вероятно, делает то же самое, что и ваши «другие запросы».
Если вас интересуют логические языки в целом, вы можете также взглянуть на язык Карри (там все нечистые функции «приостанавливаются» до тех пор, пока не будет унифицировано значение, не определенное yed).
is / 2
очень низкоуровневый и ограниченный. Как вы правильно заметили, он не может использоваться во всех направлениях и, следовательно, не является истинным соотношением.
Для обратимой арифметики используйте решатели ограничений системы Prolog .
Например, руководство SWI-Prolog CLP (FD) содержит следующее определение n_factorial / 2
:
:- use_module(library(clpfd)).
n_factorial(0, 1).
n_factorial(N, F) :- N #> 0, N1 #= N - 1, F #= N * F1, n_factorial(N1, F1).
Следующие примеры запросов показывают, что его можно использовать во всех direction:
?- n_factorial(47, F).
F = 258623241511168180642964355153611979969197632389120000000000 ;
false.
?- n_factorial(N, 1).
N = 0 ;
N = 1 ;
false.
?- n_factorial(N, 3).
false.
Конечно, это определение все еще основывается на унификации, и поэтому вы не можете вставлять произвольные целочисленные выражения. Такой термин, как 2-2
(который равен - (2,2)
в префиксной записи), не противоречит 0
. Но вы можете легко разрешить это, если переписываете это в:
:- use_module(library(clpfd)).
n_factorial(N, F) :- N #= 0, F #= 1.
n_factorial(N, F) :- N #> 0, N1 #= N - 1, F #= N * F1, n_factorial(N1, F1).
Пример запроса и его результат:
?- n_factorial(2-2, -4+5).
true .
Есть некоторые вещи, которые вы должны помнить при изучении Prolog:
Не существует неявного возвращаемого значения, когда вы вызываете предикат. Если вы хотите получить значение из вызова, вам нужно добавить дополнительные аргументы, которые могут быть использованы для "возврата" значения, второй аргумент в вашем f/2
предикате. Хотя этот предикат более многословен, он имеет то преимущество, что легко возвращает много значений.
Это означает, что автоматическая "оценка" аргументов в вызове на самом деле совершенно бессмысленна, поскольку нет значения для возврата, и она не выполняется. Поэтому нет вложенных вызовов, в этом отношении Пролог плоский. Поэтому, когда вы вызываете f(A-1, D)
, первым аргументом f/2
является структура A-1
, или на самом деле -(A, 1)
, поскольку -
является инфиксным оператором. Поэтому, если вы хотите получить значение из вызова foo
в вызов bar
, вы должны явно использовать переменную для этого, например:
foo(..., X), bar(X, ...),
Поэтому вам нужен специальный предикат, который заставляет арифметическую оценку, is/2
. Его второй аргумент - это структура, представляющая арифметическое выражение, которое он интерпретирует, оценивает и объединяет результат с первым аргументом, который может быть либо переменной, либо числовым значением.
Хотя в принципе вы можете выполнять обратные действия с большинством вещей, это не так. Обычно это возможно только для простых предикатов, работающих со структурами, хотя есть несколько очень полезных случаев, когда это возможно. is/2
не работает в обратном направлении, это было бы исключительным случаем, если бы работало.
Вот почему вам нужны дополнительные переменные C
и D
и вы не можете заменить C is A-1, f(C, D)
на f(A-1,D)
.
(Да, я знаю, что в Прологе нельзя делать вызовы, а можно оценивать цели, но мы исходили из функциональной точки зрения)