
- •Снова поиск с возвратом
- •Листинг 13.3. Программа сh06e03.Рго
- •Преимущества рекурсии
- •Оптимизация хвостовой рекурсии
- •Как задать хвостовую рекурсию
- •Листинг 13.5. Программа ch06e05.Pro
- •Листинг 13.6. Программа ch06e06.Pro
- •Использование аргументов в качестве переменных цикла
- •Листинг 13.7. Программа ch06e07.Pro
Листинг 13.5. Программа ch06e05.Pro
% В 32-битной архитектуре эти примеры будут выполняться достаточно долго, занимая % много памяти и значительно уменьшая общую производительность системы.
predicates
badcount1(long)
badcount2(long)
badcount3(long)
check(long)
clauses
% badcount1: Рекурсивный вызов - не последний шаг.
badcount1(X):-
write ('\r',X),
NewX = X+l,
badcount1(NewX),
nl.
% badcount2: Это предложение, которое не выполняется во время
% осуществления рекурсивного вызова
badcount2(X):-
write ('\r',X),
NewX = X+l,
badcount2(NewX).
badcount2(X):- X < 0,
write("X отрицательно.") .
% badcount3: Непроверенная альтернатива в процедуре,
% вызванной перед рекурсивным вызовом.
badcount3(X):-
write ('\r',X),
NewX = X+l,
check(NewX),
badcount3(NewX).
check(Z):- Z >= 0.
check(Z):- Z < 0.
Заметьте, что badcount2 и badcount3 хуже, чем badcount1, потому что они генерируют точки возврата.
Вероятно, вы сейчас думаете, что невозможно гарантировать, что процедура является оптимизированной хвостовой рекурсией. Хотя довольно просто сделать рекурсивный вызов в последней подцели заключительного предложения, но как гарантировать, что в любых других вызываемых предикатах нет альтернатив?
Отсечение позволяет отвергать все возможные излишние альтернативы. Чтобы установить cut, необходимо использовать директиву компилятора check_determ.
Вы можете исправить badcount3 следующим образом (модифицируя его имя):
cutcount3(X) :-
write ('\r',X),
NewX = X+l,
check(NewX),
!,
cutcount3(NewX).
Команда "отсечение" означает "сжечь мосты за собой", или, точнее, "однажды достигнув этой точки, не обращать внимания на альтернативные предложения этого предиката и альтернативные решения предыдущих подцелей в данном предложении". Что точнее – решайте сами. Поскольку альтернативы исключаются, фрейм стека не нужен и рекурсивный вызов может свободно идти дальше.
"Отсечение" также эффективно и в badcount2, если переместить проверку из второго предложения в первое:
cutcount2(X) :-
X >= 0,
!,
write('\r',X),
NewX = Х+1,
cutcount2(NewX).
cutcount2(X) :-
write("X is negative.").
Отсечение – это действительно решение. Оно используется всякий раз, когда альтернативы нам не интересны. В исходной версии предыдущего примера второе предложение должно было оставлять выбор, т. к. первое предложение не содержало проверки Х. Переместив проверку в первое предложение и отрицая ее, решение можно принять уже там, а отсечение установить в соответствии с утверждением: "Теперь я знаю, что я не должен писать, что Х отрицателен".
То же касается cutcount3. Предикат check показывает ситуацию, когда вы хотите совершить некую дополнительную операцию над Х, основанную на знаке. Однако код для check недетерминирован, и отсечение после его вызова – это все, на что вам надо решиться. Однако вышесказанное немного искусственно – возможно, было бы правильнее, чтобы check был детерминирован:
check(Z) :-Z >= 0,
!,
... % использование Z
check(Z) :-Z < 0,
... % использование Z
Проверка во втором предложении check – полное отрицание проверки в первом, поэтому check можно переписать как:
check(Z) :-
Z >= 0,
!,
%. . .
Если отсечение выполняется, компьютер предполагает, что непроверенных альтернатив нет, и не создает стековый фрейм. Программа ch06e06.pro (листинг 13.6) содержит измененные версии badcount2 и badcount3.