Функциональный стиль предикатов
Одной из отличительных особенностей Visual Prolog 7 от ISO‑Прологов является то, что предикаты можно использовать в качестве функций. Здесь следует отметить, что любой предикат Пролога имеет своё значение (истина или ложь) в области истинности. Однако только у предикатов, объявленных как функции, кроме области истинности есть ещё область значений, подобная той, какая есть у математических функций. И эти области не надо путать и смешивать.
Если проводить аналогию с императивными языками, то можно сказать, что предикаты подобны процедурам, а предикаты-функции подобны функциям императивных языков. Эта аналогия не совсем точна, но, во всяком случае, показывает отличие предикатов от предикатов-функций.
Пример 3. Модифицируем рассмотренный в примере 1 предикат ряд/2, так, чтобы он приобрёл функциональный стиль. Этот предикат должен быть недетерминированным и возвращать очередное значение Value члена арифметической прогрессии, начиная с начального члена From, до конечного члена UpTo, с шагом равным +1. Объявим такой предикат в виде функции:
class predicates
ряд : (integer From, integer UpTo) -> integer Value nondeterm.
Как можно увидеть в этом объявлении, характерный признак функции это стрелка, показывающая домен значения функции. Отсутствие шаблона потока аргументов говорит о том, что все аргументы являются входными.
Опишем этот рекурсивный предикат-функцию в виде:
clauses
ряд(N,K) = N :- N<=K. % условие продолжения рекурсии
ряд(N,K) = ряд(N+1,K) :- N<K. % рекурсия с проверкой
Первое предложение содержит условие продолжения и окончания рекурсии N<=K и возвращаемое значение N. Отсечение мы здесь не ставим, так как при его наличии мы сможет получить только один член арифметической прогрессии – первый.
Второе предложение также содержит условие продолжения рекурсии N<K и рекурсивный вызов функции ряд(N+1,K).
Исходный текст консольного приложения выглядит так:
implement main
open core, console
constants
className = "com/visual-prolog/main".
classVersion = "$JustDate: $$Revision: $".
class predicates
ряд : (integer From, integer UpTo) -> integer Value nondeterm.
clauses classInfo(className, classVersion).
ряд(N,K) = N :- N<=K. ряд(N,K) = ряд(N+1,K) :- N<K.
run():- init(), write(ряд(4,8)," "), fail; write("\nПрограмма завершена"),
_=readchar().
end implement main
goal
mainExe::run(main::run).
После запуска программы Вы будете последовательно получать значения членов арифметической прогрессии только с помощью отката назад.
Пример 4. Давайте посмотрим, как можно разворачивать бесконечный ряд чисел, указав только первый член ряда. Для этого удалим все упоминания в программе о конечном члене ряда UpTo. А также для задержки бесконечного цикла на каждом витке, добавим в цель run() ожидание ввода с клавиатуры перед предикатом принудительного отката назад:
implement main
open core, console
constants className = "com/visual-prolog/main". classVersion = "$JustDate: $$Revision: $".
class predicates ряд : (integer From) -> integer Value multi.
clauses classInfo(className, classVersion).
ряд(N) = N. ряд(N) = ряд(N+1).
run():- init(), write(ряд(-2)), _=readchar(), fail; write("\nПрограмма завершена"), _=readchar().
end implement main
goal
mainExe::run(main::run).
Если перед предикатом принудительного отката назад не делать ожидание ввода с клавиатуры, то программа будет выводить все числа до максимального числа из домена integer .
Пример 5. Разработаем рекурсивный предикат геом/3, в функциональном стиле, который возвращает при откате назад заданное количество Count членов арифметической прогрессии, начиная с начального значения From и с шагом +2. Объявим предикат-функцию геом/2 с нижеуказанными доменами:
class predicates
ряд : (integer From, integer Count) -> integer Value nondeterm.
Опишем предложения предиката:
ряд(N,0) = N :- !.
ряд(N,Count) = N :- Count>0.
ряд(N,Count) = ряд(N+1,Count-1) :- Count>0.
Первое предложение предиката содержит условие останова рекурсии Count=0 и возвращаемое значение функции N.
Второе предложение содержит условие продолжения рекурсии Count>0 и возвращаемое значение функции N.
Третье предложение содержит условие продолжения рекурсии Count>0 и собственно рекурсивный вызов функции.
Исходный текст консольного приложения выглядит так:
implement main
open core, console
constants
className = "com/visual-prolog/main".
classVersion = "$JustDate: $$Revision: $".
class predicates
ряд : (integer From, integer Count) -> integer Value nondeterm.
clauses classInfo(className, classVersion).
ряд(N,0) = N :- !.
ряд(N,Count) = N :- Count>0.
ряд(N,Count) = ряд(N+1,Count-1) :- Count>0.
run():- init(), write(ряд(1,8)," "), fail; write("\nПрограмма завершена"), _=readchar().
end implement main
goal
mainExe::run(main::run).
Задание 4. Написать предикат вычисления факториала.
Задание 5. Написать предикат-функцию вычисления факториала.
Задание 6. Написать предикат вычисления n-го члена ряда Фибоначчи. Подсказка: n-й член ряда Фибоначчи равен сумме: fib(n) = fib(n‑1)+fib(n‑2). Нулевой и первый члены ряда равны единице: fib(0) = 1, fib(1) = 1.
Задание 7. Написать предикат-функцию вычисления n-го члена ряда Фибоначчи.
Задание 8. Написать предикат-функцию для вычисления функции Аккермана. Эта функция часто используется для оценки функциональных языков. Подсказка: функция Аккермана имеет следующее математическое описание:
ack(0, x) = x+1
ack(x, 0) = ack(x-1, 1)
ack(x, y) = ack(x-1, ack(x,y-1))
Проверьте с помощью Вашего предиката значения функции Аккермана для следующих пар аргументов:
ack(3,6,) = 509
ack(3,7,) = 1021
ack(3,8,) = 2045
ack(3,9,) = 4093
ack(3,10,) = 8189
Задание повышенной сложности 1. Написать предикат, который будет истинным, если вводимое с клавиатуры число является простым. Подсказка: число является простым, если это число делится с остатком на все меньшие числа вплоть до двойки. Операция вычисления остатка от деления называется div. Например, Остаток = 7 div 3. В данном случае остаток равен единице.
Задание повышенной сложности 2. Написать предикат, который вычисляет наибольший общий делитель (НОД) двух чисел, вводимых с клавиатуры. Например, числа 8 и 12 имеют НОД, который равен 4, так как и 8 и 12 делятся без остатка на 4. Подсказка: Для этого примера надо проверить числа от 8 до 1 на роль наибольшего общего делителя.
