Ваш код:
map_list([], []).
Это значение []
отображается на []
, или «список букв []
находится в (двунаправленном) отношении map_list
со списком целые числа []
". Хорошо!
map_list([], _).
Это означает, что []
сопоставлен с чем угодно, или «список букв []
находится в (двунаправленном) отношении map_list
с чем-либо во вселенной компьютера». ПЛОХО!
map_list([Head|Tail], Ans) :- map(Head, N),
append(Ans, [N], NewAns),
map_list(Tail, NewAns).
Это означает, что «непустой список букв [Head|Tail]
находится в (двунаправленном) отношении map_list
с Ans
, определяемым следующим образом:
map(Head, N)
с обычным значением. - Предопределенное отношение
append
существует между двумя списками Ans
, [N]
и NewAns
. На этом этапе Ans
имеет вид но без ограничений, если вы выполнили запрос map_list([j,k],Ans)
, поэтому Пролог будет знать только, что NewAns
- это список, начинающийся с Ans
(единственное, что мы знаем о нем, это то, что он должен быть списком) и заканчивающийся на члене N
. На самом деле, вы используете append
в обратном направлении. NewAns
рекурсивно уточняется в map_list(Tail, NewAns)
, но на данный момент мы даже не уверены, что мы являемся вычисления.
В любом случае, вы получаете длинный список неограниченных переменных («мусор»):
?- map_list([j,k],X).
X = [] ;
X = [_7280] ;
X = [_7280, _7292] ;
X = [_7280, _7292, _7304] ;
X = [_7280, _7292, _7304, _7316] ;
X = [_7280, _7292, _7304, _7316, _7328]
Давайте исправим это с помощью
- Отбрасывание соответствия «что-нибудь»
map_list([], _).
- Правильно используя
append
для построения списка результатов .
Примерно так:
map(j, 0).
map(k, 1).
map_list([], []).
map_list([Head|Tail], Ans) :- map(Head, N),
append([N], TailAns, Ans),
map_list(Tail, TailAns).
Это работает.
?- map_list([j,k],X).
X = [0, 1].
Обратите внимание на поток информации. В отличие от императивных языков, информация во время append
пока еще неполна. Мы знаем N
, было ли оно передано в запросе, но мы еще ничего не знаем TailAns
, который еще предстоит построить. Это в полной мере определяется только рекурсивным вызовом. (Компилятор или виртуальная машина Prolog могут оптимизировать это, чтобы все oop изменили структуру в куче, а не в стеке, зависит.)
Вы можете написать следующее для выполнения "append
по возвращении из рекурсивный вызов ", что больше похоже на то, что сделано в императивных языках:
map(j, 0).
map(k, 1).
map_list([], []).
map_list([Head|Tail], Ans) :- map(Head, N),
map_list(Tail, TailAns),
append([N], TailAns, Ans).
Приложение для ответа на вопрос комментария
Мне нравится пытаться объяснить Пролог. Это всегда сбивает с толку, но приносит новые перспективы. (Или вы можете попробовать короткую, но дорогую The Reasoned Schemer , но сначала вам нужно узнать о Scheme).
Хорошо, так:
map_list([Head|Tail], Ans) :-
map(Head, N),
append([N], TailAns, Ans),
map_list(Tail, TailAns).
A TailAns
появляется на ровном месте в теле предиката (или процедуры). Это переменная "fre sh" (нет необходимости сначала объявлять ее в Прологе). В состоянии вычисления, при котором TailAns
появляется в первый раз , об этом еще ничего не известно, оно не ограничено.
Чтобы лучше понять, что происходит, замените слово «переменная» словом «классная доска». Затем мы можем «создать пустую / fre sh доску», «обратиться к доске по имени», «передать доску другому предикату (или процедуре)», иметь предикат «уточнить все, что находится на доске», указав дополнительные детали. Доска также может быть «общей для вызовов», и на нее можно ссылаться по ее имени в нескольких местах одного и того же вызова, в том числе посредством содержимого других досок:
A = g(B,1,C), B = h(a,k,C).
Это утверждение о досках A
, B
, C
, где мы утверждаем, что содержимое классной доски A
равно g(B,1,C)
, а содержимое классной доски B
равно h(a,k,C)
. Это утверждение может быть или не быть правильным в любой момент времени, в зависимости от того, что другие части кода уже заявили об этих классных досках. Мы ничего не говорим о содержимом классной доски C
.
Аналогично, вызов в Prolog REPL (верхний уровень) для предиката append/3
:
? - append ([1,2 ], [3], C).
создает три доски,
- одну без имени с
[1,2]
как содержимое - одну без имени с
[3]
как контент - один с именем
C
без / неуказанный контент («переменная fre sh»)
Задача append/3
теперь состоит в том, чтобы уточнить содержимое этих трех досок так, чтобы содержимое доски 3 содержало список, представляющий собой конкатенацию содержимого досок 1 и 2.
append/3
реализует не функцию, а отношение (которое является обобщением функции) или, по крайней мере, пытается достичь этого идеала. В отличие от функции, в которой информация обязательно передается от входного аргумента к выходному результату, информация между аргументами отношения течет более свободно. Например, вы можете получить входные аргументы с учетом выходных данных. Так обстоит дело здесь.
?- append(X,[1,2],[3]).
false.
Ну, решения для вышесказанного не существует. Но:
?- append(X,[1,2],[first,1,2]).
X = [first] ;
false.
Используя мысленный образ классной доски, append/3
улучшит классную доску X
таким образом, что объединение X
и [1,2]
даст [first,1,2]
(последние два написаны на разных классных досках безымянный на высшем уровне). Это достигается путем размещения [first]
на доске с именем на верхнем уровне X
.
То же самое с определением результата для списков, которые должны быть объединены:
?- append([first],[1,2],X).
X = [first, 1, 2].
То же самое с определение второго аргумента с учетом первого и результата конкатенации:
?- append([first],X,[first,1,2]).
X = [1, 2].
Неограниченные классные доски приводят к угадыванию, при этом доски fre sh вытаскиваются из воздуха с именем _somenumber
и появляются:
?- append(X,Y,Z).
X = [], Y = Z ;
X = [_5070], Z = [_5070|Y] ;
X = [_5070, _5082], Z = [_5070, _5082|Y] ;
etc.
Доска может, конечно, ссылаться на другие доски через имена переменных, потенциально одна и та же переменная появляется во многих местах:
?- append([1,X,X],[1,X,X],[1,2|R]).
X = 2,
R = [2, 1, 2, 2].
Так круто!
Изображение с доски помогает объяснить, что информация предоставляется для предиката по вызову, и предикат может вернуть информацию обратно, изменив указанную доску.
Обратите внимание, что содержание классной доски становится еще более точным путем заполнения неуказанных переменных. Он никогда не становится менее точным, выбивая «знать вещи» и заменяя их переменными.
Однако, если мы попросим невозможное отображение на доске, произойдет сбой вычислений и «вернется» в предыдущее состояние для другой попытки: построенные классные доски будут уничтожены, а более ранние классные доски будут восстановлены.
Итак, вернемся ко второй строке
map_list([Head|Tail], Ans) :- map(Head, N),
append([N], TailAns, Ans),
map_list(Tail, TailAns).
append/3
и теперь попробуем определить, дает ли объединение списка [N]
и списка TailAns
список Ans
.
Будет либо
- Сказать
true
и обновить доски N
, TailAns
и Ans
, и вычисления могут продолжаться. - Сказать
false
и вычисления "backtracks".
В данном случае append/3
сможет сказать, что содержимое классной доски Ans
точно равно [N|TailAns]
, , но не более .
На самом деле, на этом этапе вычислений на доске есть какое-то конкретное значение N
, но TailAns
совершенно неизвестно, просто это должен быть какой-то список (и это даже не проверено, так как Пролог не имеет типов).
Что TailAns
расшифровывается как становится более конкретным при последующем рекурсивном вызове map_list(Tail, TailAns)
. После самого глубокого рекурсивного вызова он будет содержать []
, и, таким образом, Ans
, построенный ранее, станет немного более точным, фактически с TailAns
, который теперь известен как []
, он трансформируется из [N|TailAns]
в [N|[]]
, записанный просто как [N]
или как N
, как известно, в этот момент равен 1, [1]
. Это также означает, что [N|TailAns]
вызывающего абонента будет уточнено с его [N|TailAns]
до [N|[1]]
, записанного просто как [N,1]
или, как N
известно в контексте вызывающего абонента. быть 0, [0,1]
.