На этом шаге мы рассмотрим составные объекты данных и функторы.
Составные объекты данных позволяют интерпретировать некоторые части информации как единое целое таким образом, чтобы затем можно было легко разделить их вновь. Возьмем, например, дату "октябрь 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)
date("April",14,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")).
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 для указания разницы между объектами.
Важная особенность составных объектов состоит в том, что они позволяют легко передавать группы величин, как один аргумент. Рассмотрим в качестве примера ведение телефонной базы данных. Допустим, вы хотите включить и базу дни рождения ваших друзей и родственников. Для чтого пришлось бы написать программу, часть которой приведена ниже:
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
Заметим, что необходимы две переменные: одна для хранения полного имени человека, а другая - для хранения дня рождения. Это достигается за счет использования составных объектов.
Так как нас интересует месяц рождения человека, используются анонимные переменные для даты и года рождения.
На следующем шаге мы рассмотрим объявления составных доменов.