Шаг 35.
Основы логического программирования.
Составные объекты данных и функторы

    На этом шаге мы рассмотрим составные объекты данных и функторы.

    Составные объекты данных позволяют интерпретировать некоторые части информации как единое целое таким образом, чтобы затем можно было легко разделить их вновь. Возьмем, например, дату "октябрь 15, 1991". Она состоит из трех частей информации - месяц, день и год. Представим ее на рис. 1, как древовидную структypy.


Рис.1. Древовидная структура даты

    Вы можете сделать это, объявив домен, содержащий составной объект date:

   domains
     date_cmp = date(string,integer,integer)
а затем просто записать:
   D=date("October",15,1991).

    Такая запись выглядит как факт Пролога, но это не так - это объект данных, который вы можете обрабатывать наряду с символами и числами. Он начинается с имени, называемого функтором (в данном случае date), за которым следуют три аргумента.

    Обратите внимание, что функтор в Прологе - не то же самое, что функция и других языках программирования; это просто имя, которое определяет вид составного объекта данных и объединяет вместе его аргументы.


    Замечание. Функтор не обозначает, что будут выполнены какие-либо вычисления.

    Аргументы составного объекта данных могут сами быть составными объектами. Например, вы можете рассматривать чей-нибудь день рождения (рис. 2), как информацию со следующей структурой:


Рис.2. Древовидная структура даты рождения

    На языке Пролог это выглядит следующим образом:

   birthday(person("Leo", "Jensen"),date("Apr ", 14 , 1991))

    У составного объекта birthday в этом примере есть две части: объект person ("Leo","Jensen") и объект date("Apr",14,1900). Функторами для этих объектов будут person и date.

Унификация составных объектов

    Составной объект может быть унифицирован с простой переменной или с составным объектом (возможно, содержащим переменные в качестве частей во внутренней структуре), который ему соответствует. Это означает, что составной объект можно использовать для того, чтобы передавать целый набор значений как единый объект, и затем применять унификацию для их разделения. Например:

    date("April",14,1960)
сопоставляется с X и присваивает X значение date("April ",14, 1960). Также
    date("April",14,1960)
сопоставляется с date(Mo,Da,Yr) и присваивает переменным Mo="April", Da=14 и Yr = 1960.

Использование знака равенства для унификации составных объектов

    Пролог осуществляет унификацию в двух случаях. Во-первых, когда цель сопоставляется с заголовком предложений. Во-вторых, через знак равенства (=), который является инфиксным предикатом (предикатом, который расположен между своими аргументами, а не перед ними).

    Фактически, Пролог выполняет операцию присваивания для унификации объектов по разные стороны знака равенства. Это свойство полезно для нахождения значений аргументов составного объекта. Например, программа pro35_1.pro проверяет, совпадают ли фамилии у двух людей, и затем дает второму человеку тот же адрес, что и у первого.

    domains
      person=person(name,address)
      name=name(first,last)
      address=addr(street,city,state)
      street=street(number,street_name)
      city,state,street_name=string
      first,last=string
      number=integer
   goal
      P1=person(name(jim,mos), addr(street(5,"1st st"), igo, "CA")),
      P1=person(name(_,mos), Address), 
      P2=person(name(jane,mos), Address), 
      write("P1=",P1) ,nl, 
      write("P2=",P2),nl.
    Текст этой программы можно взять здесь.

Использование нескольких значений как единого целого

    Составные объекты могут рассматриваться в предложениях Пролога как единые объекты, что сильно упрощает написание программ. Рассмотрим, например, факт:

    owns(john,book("From Here to Eternity","James  Jones")).
в котором утверждается, что у Джона есть книга "From Here to Eternity" (Отсюда в вечность), написанная James Jones (Джеймсом Джонсом). Аналогично можно записать:
    owns(john,horse(blacky)).
что означает:

    John owns a horse named blacky.(У Джона есть лошадь Блеки.)

    Составными объектами в этих двух примерах являются:

    book("From Here to Eternity","James Jones")
и
    horse(blacky)
    Если вместо этого описать только два факта:
   owns(john,"From Here to Eternity"), 
   owns(john,blacky).

то нельзя было бы определить, является ли blacky названием книги или именем лошади. С другой стороны, можно использовать первый компонент составного объекта - функтор для распознавания различных объектов. Этот пример использует функторы book и horse для указания разницы между объектами.


    Замечание. Составные объекты состоят из функтора и объектов, принадлежащих этому функтору, например: functor(objectl,object2,..., objectN).

Пример использования составных объектов

    Важная особенность составных объектов состоит в том, что они позволяют легко передавать группы величин, как один аргумент. Рассмотрим в качестве примера ведение телефонной базы данных. Допустим, вы хотите включить и базу дни рождения ваших друзей и родственников. Для чтого пришлось бы написать программу, часть которой приведена ниже:

   predicates
      phone_list(symbol First, symbol Last, symbol Phone,
                       symbol Month, integer Day, integer Year)
   clauses
      phone_list(ed,willis,422_02_08,aug,3,1955).
      phone_list(chris,grahm,433_99_06,may,12,1962).

    Обратите внимание, что в факте phone_list шесть аргументов, пять из них могут быть разбиты (рис. 3) на два составных объекта.


Рис.3. Разбивка составных объектов

    Может оказаться более полезным представлять факты так, чтобы они отражали эти составные объекты данных. Вернувшись на шаг назад, видим, что person (лицо) - это отношение, а имя и фамилия - объекты. Также, birthday (день рождения) - это отношение между тремя аргументами: месяцем, днем и годом. В представлении Пролога они могут быть записаны следующим образом:

   person(First_name,Last_name)
   birthday(Month,Day,Year)
    Вы можете теперь переписать свою маленькую базу данных с включением этих составных объектов как части вашей базы данных.
   domains
      name=person(symbol First,symbol Last)
      birthday = b_date(symbol Month, integer Day, integer Year)
      ph_num = symbol % Phone_number
   predicates
      phone_list(name, ph_num, birthday)
   clauses
      phone_list(person(ed,willis),"422-02-08",b_date(aug,3,1955)).
      phone_list(person(chris,grahm),"433-99-06",b_date(may,12,1962)).

    В эту программу введены два определения составных доменов. Мы рассмотрим некоторые подробности этих составных структур данных далее, а сейчас остановимся на применении таких составных объектов.

    Предикат phone_list теперь содержит три аргумента, что отличается от шести предыдущем примере. Иногда разбиение данных и составном объекте делает более ясной логику программы и может помочь в обработке данных. А теперь добавим несколько правил в нашу маленькую программу. Допустим, вы хотите создать список людей, у которых день рождения в этом месяце. Ниже приведена программа pro35_2.pro, которая решает эту задачу, используя встроенный предикат date для получения даты из внутреннего календаря компьютера. Предикат date возвращает текущие год, месяц и день из календаря компьютера.

   domains
      name=person(symbol,symbol) % (первый,последний)
      birthday=b_date(symbol,integer,integer)  % (месяц,день,год)
      ph_num=symbol % телефонный номер
   predicates
      phone_list(name,ph_num,birthday)
      get_months_birthdays() 
      convert_month(symbol,integer)
      check_birthday_month(integer,birthday)
      write_person(name)
   clauses
      get_months_birthdays:-
         write("************ This Month's Birthday List ***********+*"),nl,
         write(" First name\t\t Last Name\n"),
         write("*****************************************************"),nl,
         date(_, This_month, _), % получить месяц из системных часов
         phone_list(Person, _, Date),
         check_birthday_month(This_month, Date),
         write_person(Person) ,
         fail.
      get_months_birthdays:-
         write("\n\n Press any key to continue: "),nl,
         readchar(_).
      write_person(person(First_name,Last_name)):-
         write("     ",First_name,"\t\t       ",Last_name),nl.
      check_birthday_month(Mon,b_date(Month,_,_)):-
         convert_month(Month,Monthl), 
         Mon = Monthl.
      phone_list(person(ed,willis),"7 67-84 63",b_date(jan, 3,1955)).
      phone_list(person(benjamin,thomas),"438-8400",b_date(feb,5,1985)). 
      phone_list(person(ray,william),"555-5653",b_date(mar,3,1935)).
      phone_list(person(thomas,alfred),"767-2223",b_date(apr,29,1951)).
      phone_list(person(chris,grahm), "555-1212",b_date(may,12,1962)).
      phone_list(person(dustin,robert),"438-8400",b_date(jun,17,1980)).
      phone_list(person(anna,friend),"767-8463",b_date(jun, 20,1986)).
      phone_list(person(brandy,rae),"555-5653",b_date(jul,16,1981)). 
      phone_list(person(naomi,friend),"767-2223",b_date(aug,10,1981)).
      phone_list(person(christina,lynn),"438-8400",b_date(sep,25,1981)).
      phone_list(person(kathy,ann),"438-8400",b_date(oct,20,1952)). 
      phone_list(person(elizabeth,ann),"555-1212",b_date(nov,9,1984)). 
      phone_list(person(aaron,friend) ,"767-2223",b_date(nov,15,1987)). 
      phone_list(person(jennifer,caitlin), "438-8400",b_date(dec,31,1981)).

      convert_month(jan,1). 
      convert_month(feb,2). 
      convert_month(mar,3). 
      convert_month(apr,4). 
      convert_month(may,5).
      convert_month(jun,6). 
      convert_month(jul,7).
      convert_month(aug,8). 
      convert_month(sep,9). 
      convert_month(oct,10).
      convert_month(nov,11).
      convert_month(dec,12).
   goal
      get_months_birthdays().
    Текст этой программы можно взять здесь.

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


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

    Загрузите и запустите эту программу.
  1. Программа использует окно для вывода результата.
  2. Помещает в окне заголовок, помогающий понять результат.
  3. Использует в предикате get_month__birthdays встроенный предикат date.
  4. Кроме того, программа производит поиск в базе данных и печатает список людей, родившихся в текущем месяце. Сначала ищется первый человек в базе данных. Вызов phone_list (Person,_,Date) помещает имя и фамилию человека в переменную Person, помещая функтор person целиком в Person, а день рождения - в переменную Date.

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

  5. Ваша программа может теперь передавать день рождения человека просто путем передачи переменной Date. Это происходит в следующей подцели, где программа передает текущий месяц (представленный целым числом) и день рождения в предикат check_birthday_month.
  6. Пролог вызывает предикат check_birthday_month с двумя переменными: первая переменная связана с целым, а вторая - с термом birthday. В заголовке правила, которое определяет check_birthday_month, This_month, сравнивается с переменной Mon. Второй аргумент, Date, сопоставляется с b_date(Month,_,_).
  7.     Так как нас интересует месяц рождения человека, используются анонимные переменные для даты и года рождения.

  8. Предикат check_birtday_month сначала превращает символ месяца в целое число. После того, как это сделано, Пролог может сравнить значение текущего месяца с месяцем рождения человека. Если это сравнение удачно, подцель check_birthday_month завершается успешно и обработка продолжается. Если сравнение неуспешно (человек родился в другом месяце), Пролог откатывается для поиска другого решения задачи.
  9. Следующая подцель, которую нужно обработать, - write_person. Лицо, данные которого нужно обработать, имеет день рождения в этом месяце, и поэтому в отчет попадают только правильные данные. После распечатки информации предпожение терпит неуспех, что вызывает поиск с возвратом.
  10. Поиск с возвратом всегда возвращается к последнему неудовлетворенному вызову и пытается его удовлетворить. В данной программе последний неудовлетворенный вызов - phone_list. Поэтому программа будет искать другое лицо, которое может быть обработано. Если в базе данных нет больше людей, текущее предложение терпит неуспех, Пролог пытается доказать этот вызов, просматривая базу данных дальше, но т. к. есть еще предложение get_month_birthdays, Пролог пытается доказать его, доказав подцели этого предложения.

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




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