Почему после нажатия точка с запятой программа возвращается в глубокую рекурсию? - PullRequest
3 голосов
/ 14 апреля 2020

Я пытаюсь понять функциональность точки с запятой.

У меня есть этот код:

del(X,[X|Rest],Rest).
del(X,[Y|Tail],[Y|Rest]) :-
    del(X,Tail,Rest).

permutation([],[]).
permutation(L,[X|P]) :- del(X,L,L1), permutation(L1,P).

Это простой предикат, чтобы показать все перестановки данного списка.

Я использовал встроенный графический отладчик в SWI-Prolog, потому что я хотел понять, как он работает, и я понимаю для первого случая, который возвращает список, указанный в аргументе. Вот диаграмма, которую я сделал для лучшего понимания. debug diagram

Но я не понимаю это для другого решения. Когда я нажимаю точку с запятой, она не начинается там, где она заканчивается, а начинается с некоторой глубокой рекурсии, где L=[] (как в шаге 9). Я не понимаю, рекурсия не закончилась раньше? Чтобы получить ответ, нужно было go из рекурсии, и после точки с запятой он снова глубоко в рекурсии.

Может ли кто-нибудь объяснить мне это? Заранее спасибо.

Ответы [ 2 ]

1 голос
/ 14 апреля 2020

Одна аналогия, которую я нахожу полезной при демистификации Пролога, состоит в том, что Возврат похож на Вложенные циклы , и когда все внутренние значения переменных l oop все найдены, цикл приостанавливается, переменные 'значения сообщаются, и затем цикл возобновляется.

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

Мы пишем это в Прологе как

above(0, N), between(1, N, M), Sum is M+N, is_prime(Sum).

Мы пишем это в императивном псевдокоде как

for N from 1 step 1:
  for M from 1 step 1 until N:
     Sum := M+N
     if is_prime(Sum):
        report_to_user_and_ask(Sum)

Сейчас когда вызывается report_to_user_and_ask, он печатает Sum и спрашивает пользователя, следует ли прервать или продолжить. Петли не выходят, наоборот, они просто подвешены. Таким образом, все значения переменных l oop, которые продвинули нас так далеко - и может быть больше тестов в цепочке циклов, которые иногда успешны, а иногда - неудачны - сохраняются, то есть сохраняется состояние вычислений, и вычисление готово к возобновить с этого момента, если пользователь нажимает ;.

Впервые я увидел это в реализации книги Пролога в Common Lisp в книге AI Питера Норвига. Однако он использовал отображение (Common Lisp's mapcan, который concatMap в Haskell или flatMap во многих других языках) в качестве циклической конструкции, и мне потребовались годы, чтобы увидеть, что вложенные циклы являются что это такое на самом деле.

Цели соединение выражается как вложенность петель; цели дизъюнкция выражается в виде альтернатив к l oop через.

Дальнейший поворот заключается в том, что структура вложенных циклов не фиксирована с самого начала. Это жидкость , вложенные циклы данного l oop могут быть созданы в зависимости от текущего состояния этого l oop, то есть в зависимости от текущей альтернативы, исследуемой там; циклы записываются как мы go. В (большинстве) языков, где такое динамическое создание вложенных циклов c невозможно, его можно закодировать с помощью вложенной рекурсии / вызова функции / внутри циклов. (Вот один пример , с некоторым псевдокодом.)

Если мы сохраним все такие циклы (созданные для каждой из альтернатив) в памяти даже после того, как они закончатся, мы получим Таким образом, создается дерево И-ИЛИ (упомянутое в другом ответе), пока исследуется пространство поиска и найдены решения.

(не случайно эта текучесть также является сущностью "монады" ; недетерминизм моделируется списком ; и существенным Операция списка монады - это операция flatMap, которую мы видели выше. С структурой жидкости петель это "Монада" ; с фиксированным структура это «Аппликативный функтор» ; простые циклы с без структуры (без вложенности вообще): просто «Функтор» (понятия, используемые в Haskell и т. П.). Также помогает демистифицировать их.)

Таким образом, правильный слоган может быть Возвратный путь похож на вложенные циклы, либо фиксированные, известные с самого начала, либо динамически создаваемые как мы go. Это немного дольше, хотя. :)


Вот также пример Пролога, который "как будто создает код для запуска вначале (N вложенных циклов для данного значения N), а затем запускает его. " (в SO тоже есть целый выделенный тег для него, оказывается, .)

И вот один в схеме ( "создает вложенные циклы с решением, доступным в самом внутреннем теле l oop" ), и пример C ++ ( "создает n вложенных циклов во время выполнения, фактически перечисляя двоичное кодирование 2 n , и печатая суммы из самого внутреннего l oop" ).

1 голос
/ 14 апреля 2020

Существует большая разница между рекурсией в функциональных / императивных языках программирования и в Прологе (и это действительно стало ясно только для меня в последние 2 недели или около того):

В функциональном / императивном программировании вы рекурсивны вниз по цепочке вызовов, затем вернитесь вверх, размотав стек, затем выведите результат. Все кончено.

В Прологе вы просматриваете дерево И-ИЛИ (действительно, чередующиеся узлы И и ИЛИ), выбирая предикат для вызова на узле ИЛИ («точка выбора») ), слева направо и вызывая каждый предикат по очереди на узле AND, также слева направо. приемлемое дерево имеет ровно один предикат, возвращающий ИСТИНА в каждом узле ИЛИ, и все предикаты, возвращающие ИСТИНА в каждом узле И. После того, как приемлемое дерево было построено, самой процедурой поиска мы (то есть «курсор поиска») на крайнем правом нижнем узле .

Успех в построении приемлемого дерева также означает, что было найдено решение запроса, введенного в Прологе Toplevel (REPL): значения переменных выводятся, но дерево сохраняется (если нет точек выбора).

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

Если вы сейчас нажмете ;, поиск не возобновляется с верхней части дерева, а с нижней, т. Е. С текущей позиции курсора: у ближайшего родительского ИЛИ узла запрашивается больше решений. Это может привести к большому поиску, пока не будет построено новое приемлемое дерево, мы находимся в новом самом правом нижнем узле. Новые значения переменных выводятся, и вы можете снова ввести ;.

Этот процесс циклически повторяется до тех пор, пока больше не будет построено приемлемое дерево, на котором выводится false.

Обратите внимание, что использование этого И-ИЛИ в качестве проверяемой и изменяемой структуры данных во время выполнения позволяет развертывать некоторые магические приемы.

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

...