Ответ @Junuxx - это один шаг к решению; в вашей программе есть еще одна проблема. Но сначала шаг назад: @Junuxx заметил проблему и исправил ее. Хороший. Но как вам обнаружить такую проблему? На самом деле, вы спросили »бесконечный цикл, но как?»
Что хорошо в Прологе, так это то, что вы часто можете локализовать циклическую программу до очень маленького фрагмента программы. Такой фрагмент называется failure-slice а>. То есть: Больше никаких неприятных ощущений при чтении длинных программ!
Вернемся к вашей программе. Если вы загрузите его, вы получите сообщение вроде:
Warning: /usager/SO/paranoid.pl:13:
Singleton variables: [Result]
Что уже дает вам намек на что-то, скорее всего, неправильное. Увы, на данный момент это не вас больше всего беспокоит. Ваша самая большая проблема в том, что цель test
зацикливается!
Локализация незавершенности
Итак, как вы можете с минимальными усилиями понять, что на самом деле зацикливается?
Один из способов — запустить трассировщик, который покажет вам шаг за шагом, как Пролог выполняет эту программу. Однако трассировщик покажет вам много несущественных деталей. Деталь, которую не нужно понимать при программировании на Прологе. Детали, которые заполняют ваш разум так, что есть вероятность, что вы полностью упустите настоящую проблему. Поэтому, если вы не хотите проводить время на экранах, заполненных мерцающими линиями, держитесь подальше от трассировщиков.
Другой способ — добавить цели false
в свою программу. Помните, что ваша программа уже зациклена, так что такие лишние цели вам сильно не повредят. Зачем разрушать вашу программу этими false
целями, которые вы вообще не хотели писать? Это потому, что эти false
цели помогут вам обнаружить виновника незавершения в вашей программе, скрывая «нерелевантные» части. Это так благодаря следующему наблюдению:
Если фрагмент сбоя (= ваша программа, подвергшаяся вандализму) не завершается, тогда исходная программа также не завершается.
В некотором смысле срез отказа является причиной того, что ваша программа не завершается. Или, говоря более строго: пока вы не измените видимую часть в срезе отказа; то есть, пока вы только пытаете удачу, изменяя части, которые не видны в срезе сбоя, проблема не исчезнет! Гарантировано! Это не самая лучшая гарантия, но это лучше, чем быть слепым.
Вот что я получаю в качестве среза отказа. Я удалил printSentence/1
, потому что он больше не используется во фрагменте. И я добавил определение append/3
. Некоторые прологи предлагают append/3
в качестве встроенного предиката, который вы не можете изменить. В этом случае используйте другое имя, например local_append/3
, только не забудьте заменить все вхождения!
append([], Zs, Zs) :- false.
append([X|Xs], Ys, [X|Zs]) :-
append(Xs, Ys, Zs), false.
transform([], Result) :- false.
transform([Word|Rest], Result) :-
replace(Word, Replacement),
append(Result, Replacement, NewResult), false,
transform(Rest, NewResult).
replace(my, your) :- false.
replace(i, you) :- false.
replace(you, me).
replace(am, are) :- false.
replace(Word, Word) :- false.
test :-
X = [you, are, my, only, hope],
transform(X, Result), false,
printSentence(Result).
Когда я загружаю этот фрагмент отказа, я получаю:
?- test.
ERROR: Out of local stack
Что является хорошим признаком того, что программа не завершается. Вместо этого на моем ограниченном оборудовании он исчерпывает все ресурсы. ((Чтобы быть педантичным, эта программа все еще может завершиться, ей может потребоваться слишком много ресурсов. Но помните: у нас есть эти if циклы отказоустойчивости, то вся программа В любом случае, доказать незавершенность среза-среза часто будет проще, так как фрагмент короче)).
Некоторые наблюдения: Первоначально transform/2
использовалось рекурсивно. Теперь это уже не так. Единственная оставшаяся рекурсия находится в пределах append/3
. Итак, сначала я смотрю на цель append(Result, Replacement, NewResult)
и пытаюсь понять, какие могут быть переменные. Самый простой — это 3-й аргумент: NewResult
— единственное вхождение в нашем фрагменте, поэтому мы можем заменить его на _
. Переменная второго аргумента Replacement
всегда будет me
. И первым аргументом (здесь я должен теперь посмотреть на test/0
) будет неконкретизированная переменная. Итак, мы должны рассмотреть цель append(_, me, _)
.
Просто запустите append(_, me, _), false
, чтобы убедиться, что эта цель не прерывается! Вы также можете увидеть это, проверив слайс отказа. Вот еще раз:
append([], Zs, Zs) :- false.
append([X|Xs], Ys, [X|Zs]) :-
append(Xs, Ys, Zs), false.
Посмотрите на Ys
: Никого это не волнует, его просто "сдали". Только первый и третий аргумент могут гарантировать завершение!
Для получения дополнительной информации см. тег failure-slice.
Хорошая печать
Действуют определенные ограничения! Пустота там, где это запрещено! Вы можете проделать вышеуказанные рассуждения только с чистой монотонной программой на Прологе. На самом деле, некоторые безобидные побочные эффекты, такие как те, что есть в вашей программе, тоже в порядке вещей. Пока они не влияют на поток управления.
Другая проблема
Есть еще одна проблема с вашей программой. Запустите printSentence([you]), false
, чтобы увидеть это! Откаты и побочные эффекты не так уж легко сочетаются друг с другом. Для новичка лучше всего вообще избегать побочных эффектов. См. этот вопрос и этот ответ например, как удалить бесполезные побочные эффекты в задачах программирования. Почему бы не позвонить transform([you, are, my, only hope], Xs)
или maplist(replace,[you, are, my only, hope], Xs)
напрямую? Это позволяет вам снова сосредоточиться на соответствующих частях!
person
false
schedule
12.11.2012