Существует несколько особенностей вашего кода, которые затрудняют понимание начинающих. В частности, использование модерируемой низкоуровневой арифметики является серьезным препятствием при взаимодействии с программой в игривом (и на самом деле также серьезным) способом.
Например, чтобы понять отношение, полезно начать с самого общего запроса. Это только спрашивает: «Есть ли какое-либо решение вообще, и если да, то как выглядит решение?». В вашем конкретном примере, наиболее общий запрос выглядит следующим образом:
?- insertAt(E, Pos, Ls0, Ls).
и это почти сразу дает instantiation error
из-за не-декларативные арифметические предикаты вы используете:
?- insertAt(E, Pos, Ls0, Ls).
Pos = 0,
Ls = [E|Ls0] ;
ERROR: insertAt/4: Arguments are not sufficiently instantiated
Кроме того, вы препятствуя хорошему декларативному чтению с использованием императивного имени («insert ...»). Это делает излишне трудно создать чувство для реляционного программирования.
Поэтому я рекомендую вам: (1) Использовать более декларативное имя предиката и (2) использовать символическое представление натуральных чисел, что делает предикат более понятным и более общим.
Я буду использовать номер 0
для представления нуля, а термин s(X)
- преемник номера X
. См. successor-arithmetics для получения дополнительной информации об этом представлении.
С учетом этих изменений, код становится:
element_at(E, 0, [_|Ls], [E|Ls]).
element_at(E, s(X), [L|Ls0], [L|Ls]) :-
element_at(E, X, Ls0, Ls).
Чтобы понять эту программу, мы читаем его декларативно: Первый пункт является верноесли позиция 0
, а голова финала список E
, а хвост ... и т.д. второе предложение является истиннымеслиelement_at(E, X, Ls0, Ls)
держится, и глава ... и т.д.
Ну, самый общий запрос теперь работает намного лучше:
?- element_at(E, Pos, Ls0, Ls).
Pos = 0,
Ls0 = [_G1071|_G1072],
Ls = [E|_G1072] ;
Pos = s(0),
Ls0 = [_G1073, _G1079|_G1080],
Ls = [_G1073, E|_G1080] ;
Pos = s(s(0)),
Ls0 = [_G1073, _G1081, _G1087|_G1088],
Ls = [_G1073, _G1081, E|_G1088] .
Обратите внимание, однако, что есть что-то несправедливое здесь происходит: Где ответы на остальные позиции?Для более справедливого перечисления, мы используем length/2
, указав заранее длину списков, которые мы рассматриваем один за другим:
?- length(Ls0, _), element_at(E, Pos, Ls0, Ls).
Ls0 = [_G1124],
Pos = 0,
Ls = [E] ;
Ls0 = [_G1124, _G1127],
Pos = 0,
Ls = [E, _G1127] ;
Ls0 = [_G1124, _G1127],
Pos = s(0),
Ls = [_G1124, E] .
А теперь яснее, как различные аргументы взаимодействуют между собой, потому что вы уже видите различные примеры терминов, описываются вашим предикатом.
В самом деле, чтобы уменьшить количество аргументов и имена переменных, нам нужно следить, я часто использую DCG notation при описании списков, и я хотел бы показать вам этот альтернативный вариант тоже:
element_at(Element, 0, [_|Ls]) -->
[Element],
list(Ls).
element_at(Element, s(X), [L|Ls]) -->
[L],
element_at(Element, X, Ls).
list([]) --> [].
list([L|Ls]) --> [L], list(Ls).
?- length(Ls0, _), phrase(element_at(E, Pos, Ls0), Ls).
Ls0 = [_G1148],
Pos = 0,
Ls = [E] ;
Ls0 = [_G1148, _G1151],
Pos = 0,
Ls = [E, _G1151] ;
Ls0 = [_G1148, _G1151],
Pos = s(0),
Ls = [_G1148, E] .
После того, как вы прочитаете на ноте dcg, эта версия станет для вас понятной.
В конце концов, вы можете сказать: «Ну, это хорошо, но s(X)
нотация все еще кажется весьма странным», и вы можете использовать более широко используется индо-арабскую нотацию для целых чисел в ваших программах.
Для этого мы можем просто взять любую из версий сверху и заменить нотацию s(X)
декларативной целочисленной арифметикой с ограничениями CLP (FD). Так, например, с первой версией:
:- use_module(library(clpfd)).
element_at(E, 0, [_|Ls], [E|Ls]).
element_at(E, X, [L|Ls0], [L|Ls]) :-
X #> 0,
X #= X0 + 1,
element_at(E, X0, Ls0, Ls).
Это также работает во всех направлениях, точно так, как мы ожидаем от хорошо декларативных и общего предиката:
?- length(Ls0, _), element_at(E, Pos, Ls0, Ls).
Ls0 = [_G2095],
Pos = 0,
Ls = [E] ;
Ls0 = [_G2095, _G2098],
Pos = 0,
Ls = [E, _G2098] ;
Ls0 = [_G2095, _G2098],
Pos = 1,
Ls = [_G2095, E] .
Пожалуйста см clpfd для получения дополнительной информации, и я надеюсь, что этот пост побуждает вас думать более реляционно о вашем коде Prolog, попробовать его во всех направлениях и прочитать его декларативно. (Опишите?)
+1. Ваше решение, использующее 'nth0/4', вероятно, нужно делать на практике. Он также ведет себя так, как ожидалось, для любого шаблона создания аргументов, который я мог бы придумать. –