Шаг 33.
Основы логического программиравания.
Факты и правила в качестве процедур

    На этом шаге мы рассмотрим факты и правила в качестве процедур.

    Можно рассматривать правила Пролога как определения процедур. Например, правило:

   likes(bill,Something):-
      likes(cindy,Something)

    означает:

    "Для того чтобы доказать, что Билл любит что-то, необходимо доказать, что Синди любит это".

    Таким образом, видим, что предикаты типа:

   say_hello:-
     write("Hello"),nl.
и
   greet:-
      write("Hello,Earthlings!"),
      nl.

соответствуют подпрограммам и функциям в других языках программировании.

    Вы можете рассматривать даже факты Пролога, как процедуры; например, факт

    likes(bill,pasta)

означает:

    "Для того чтобы доказать, что Билл любит pasta, не нужно ничего делать, и если аргументы Who и What в вашем запросе likes(Who,What) - свободные переменные, то вы можете присвоить им значения bill и pasta, соответственно". Далее мы покажем, как известные процедуры программирования (условное ветвление, булевы выражения, безусловные переходы и возвращение результата вычисления) могут быть реализованы в Прологе.

Использование правил для условного ветвления

    Одно из основных различий между правилами в Прологе и процедурами в других языках программирования заключается в том, что Пролог позволяет задавать множество альтернативных определений одной и той же процедуры. Это видно по "семейной" программе pro15_2.pro на шаге 15. Человек может быть предком, будучи отцом или матерью, поэтому определение предка состоит из двух правил.

    Вы можете использовать множество определений так же, как вы применяете предложение case в Pascal, задавая множество альтернативных определений для каждого значения аргумента (или множества значений аргумента). Пролог же будет перебирать одно правило за другим, пока не найдет то, которое подходит, и затем выполнит действие, заданное правилом (как в следующей программе pro33_1.pro).

   predicates
      action(integer)
   clauses
      action (1):-
         nl,
         write("You typed 1."),nl. 
      action(2):-
         nl,
         write("You typed two."),nl.
      action(3):-
         nl,
         write("Three was what you typed."),nl.
      action(N):-
         nl,
         N<>1,N<>2,N<>3,
         write("I don't know that number!"),nl.
   goal
      write("Type a  number  from 1  to  3: "),
      readint(Choiсe),
      action(Choiсe).
    Текст этой программы можно взять здесь.

    Результат работы программы можно посмотреть на рис.1


Рис.1. Результат работы программы pro33_1.pro

    Если пользователь нажмет клавиши <1>, <2> или <3>, action будет вызвана с coответствующим значением аргумента и будет вызвано одно из первых трех правил этого примера.

Выполнение проверки в правиле

    Посмотрите более внимательно на четвертое правило для action. Оно будет сопоставлено для любого аргумента, переданного правилу. Если вы хотите быть уверенными, что оно не напечатает I don't know that number (Я не знаю такого числа) когда число попадает в правильный диапазон, - это задача для подцелей

    X<>1, X<>2, X<>3
где <> обозначает "не равно". Теперь, для того чтобы напечатать "Я не знаю такого числа", Пролог должен сначала доказать, что X не равен 1, 2 или 3. Если какая-либо из этих подцелей неуспешна, то Пролог попытается сделать откат и найти новые альтернативы. Но так как таких альтернатив нет, то остаток предложения никогда будет выполнен.


    Замечание: Предикат action подразумевает, что Choice уже связана. Если вы вызывае action со свободной переменной в качестве аргумента, то компилятор сгенеририрует ошибку.

Отсечение как GoTo

    Программа pro33_1.pro не совсем корректна из-за того, что после выбора и выполнения нужного правила Пролог продолжает поиск альтернатив.

    Вы могли бы сэкономить ресурсы и время, если бы указали, где нужно прекратить поиск альтернатив, используя отсечение. Это означает:

    "Если вы дошли до этого места, то не нужно производить откаты внутри этого правила и не нужно проверять остальные альтернативы этого правила".

    Возвращение все еще возможно, но только на более высоком уровне. Если текущее правило вызывается другими правилами, и высшие правила имеют альтернативы, они могут быть испробованы. Но отсечение отбрасывает альтернативы внутри правила и альтернативы данного правила (предиката).

    Используя отсечение (cut), программа pro33_1.pro может быть переписана следующим образом:

   predicates
      action(integer)
   clauses
      action(1):-!,
         nl,
         write("You typed  1.").
      action(2):-!,
         nl,
         write("You typed two.").
      action(3) :-!,
         nl,
         write("Three was what you typed.").
      action(_):-
         write("I don't know that number!").
   goal
      write("Type a  number  from 1  to  3: ") ,
      readint(Num),
      action(Num), nl.
    Текст этой программы можно взять здесь.

    Отсечение не имеет никакого эффекта, пока оно не будет выполнено реально. В приведенном выше примере, для того чтобы выполнить отсечение, Пролог должен войти в правило, содержащее отсечение и достичь точки, где расположено отсечение.

    Отсечение может бьпъ представлено другими примерами:

   action(X):-
      Х>3, 
      !,
      write("Too high.").
    В этом правиле отсечение не произведет никакого действия, пока не будет достигнута первая подцель X>3.

    Заметьте, что порядок правил здесь имеет значение. В программе pro33_1.pro вы могли написать правила в любом порядке; только одно из них сопоставлялось с конкретным числом. Но в примере pro33_2.pro вы должны быть уверены, что компьютер не сделает попытки выполнить правило, печатающее "Я не знаю такого числа", раньше, чем будут испробованы (и не выполнят своих отсечений) все предыдущие правила.

    Отсечения в программе pro33_2.pro иногда называют красными отсечениями, т. к. они меняют логику программы. Если вы сохраните проверки X<>1, Х<>2 и Х<>3, изменив программу только вставкой отсечений в каждом предложении, то вы сделаете зеленые отсечения. Они экономят время и, тем не менее, оставляют программу такой же правильной, как и без отсечений. Выигрыш при этом не так велик, но риск внести ошибку в программу уменьшается.

    Отсечение - это мощный, но и опасный оператор Пролога. В этом отношении oн соответствует предложению GoTo, и остальных языках программирования он многое позволяет, но делает нашу программу более трудной для понимания.

Возврат вычисленного значения

    Как мы уже видели, правила и факты Пролога могут возвращать информацию в цель, которая их вызывает. Это делается путем связывания переменных, которые были ранее не связанными.

    Факт:

   likes(bill,cindy).

    Возвращает информацию в цель

   likes(bill,Who).

    путем присваивания переменной Who значения cindy.

    Правило может возвращать тем же способом и результат вычислений. В программе pro33_3.pro приведен пример.

   predicates
      classify(integer,symbol)
   clauses
      classify(0,zero).
      classify(X,negative):-
         X < 0. 
      classify(X,positive):-
         X > 0.
    Текст этой программы можно взять здесь.

    Первый аргумент classify должен всегда получать константу или связанную переменную. Второй аргумент может быть связанной или свободной переменной, oн сопоставляется с символами zero, negative, pozitive в зависимости от значения первого аргумента.

    Здесь приведены несколько примеров правил, которые могут возвращать значения.

1. Вы можете узнать, положительно ли число:
   classify(45,positive).
   да
    Так как 45 больше 0, только третье предложение classify может быть успешным. Оно сопоставляет второй аргумент с positive. Но второй аргумент уже равен positive, поэтому сопоставление успешно, и вы получаете ответ yes (да).

2. Если cопоставление неуспешно, вы получаете ответ no (нет):
   classilу(45,negative).
   нет

    Что происходит при этом:

3. Для получения правильного ответа, а не yes или , вы должны вызвать classify со свободным вторым аргументом.
  classify(45,What).
  What=positive
  1 решение
    Что происходит в этом случае:

    На следующем шаге мы рассмотрим простые объекты данных.




Предыдущий шаг Содержание Следующий шаг