Составные Домены и Списки: различия между версиями

Материал из wikiru.visual-prolog.com

 
(не показано 35 промежуточных версий 2 участников)
Строка 2: Строка 2:


Составные домены и списки создаются с использованием встроенных (built-in) и других составных или списковых доменов. Справочная система Visual Prolog (Help) объясняет встроенные домены:
Составные домены и списки создаются с использованием встроенных (built-in) и других составных или списковых доменов. Справочная система Visual Prolog (Help) объясняет встроенные домены:
integer - целые  
*integer - целые  
real - вещественные  
*real - вещественные  
string - строковые
*string - строковые
symbol -символьные  
*symbol -символьные  
char - знаковые
*char - знаковые
sring8 - строковые 8-разрядные  
*sring8 - строковые 8-разрядные  
pointer - указатели  
*pointer - указатели  
binary - двоичные (бинарные)  
*binary - двоичные (бинарные)  
boolean - булевские  
*boolean - булевские  
object - объекты
*object - объекты


==Compound Domains and Functors==
==Составные домены и Функторы==
 
Составные домены позволяют рассматривать наборы данных как единое целое и при этом мы можете рассматривать их и раздельно. Рассмотрим, например, дату "October 15, 2003". Она состоит из трех единиц информации месяца, дня и года но было бы полезно рассматривать дату как единое целое в виде древовидной структуры:
Compound domains allow you to treat several pieces of information as a single item in such a way that you can easily pick them apart again. Consider, for instance, the date "October 15, 2003". It consists of three pieces of information the month, day, and year but it is useful to treat the whole thing as a single data with a treelike structure:


<pre>        DATE
<pre>        DATE
Строка 22: Строка 21:
       /  |  \
       /  |  \
       /  |  \
       /  |  \
   October 15  2003</pre>You can do this by declaring a domain containing the compound domain '''date''':
   October 15  2003</pre>
 
Это можно сделать путем объявления домена '''date_cmp''', содержащего  данные '''date''':
<vip>domains
<vip>domains
     date_cmp = date(string Month, unsigned Day, unsigned Year).</vip>
     date_cmp = date(string Month, unsigned Day, unsigned Year).</vip>
 
и затем писать просто, то есть
and then simply writing e.g.
 
<vip>D = date("October", 15, 2003),</vip>
<vip>D = date("October", 15, 2003),</vip>
Это выглядит как факт Пролога, но это не так – это всего лишь значение, которое можно обрабатывать почти также, как строки или числа. Такая структура начинается с имени, обычно называемым функтор (functor) (в данном случае - date), за которым следуют три аргумента.


This looks like a Prolog fact, but it isn't here – it is just a value, which you can handle in much the same way as a string or number. It begins with a name, usually called a functor(in this case date), followed by three arguments.
Обращаем Ваше внимание на то, что функтор в Visual Prolog не имеет ничего общего с функциями в языках программирования. '''''Функтор не вызывает никаких вычислений''''', это всего лишь имя, которое идентифицирует составную величину и объединяет свои аргументы.


Note carefully that a functor in Visual Prolog has nothing to do with a function in other programming languages. '''''A functor does not stand for some computation to be performed.''''' it is just a name that identifies a kind of compound value and holds its arguments together.
Аргументы составной величины могут быть, в свою очередь, составными. К примеру, Вы можете думать о чьем-либо дне рождения как о структуре данных, так, как показано:
 
The arguments of a compound value can themselves be compound. For instance, you might think of someone's birthday as an information structure like this:


<pre>            birthday
<pre>            birthday
Строка 46: Строка 42:
     /  \              / | \
     /  \              / | \
   /    \            /  |  \
   /    \            /  |  \
"Per" "Schultze"  "Apr" 14 1960</pre>In Prolog you would write this as:
"Per" "Schultze"  "Apr" 14 1960</pre>
 
На Прологе это может быть записано как:
<vip>birthday(person("Per", "Schultze"), date("Apr",14,1960)).</vip>
<vip>birthday(person("Per", "Schultze"), date("Apr",14,1960)).</vip>
В этом примере видны две подчасти составного значения birthday: аргумент person("Per", "Schultze") и аргумент date("Apr", 14, 1960). Функторами этих величин являются person и date.


In this example, there are two sub-parts in the compound birthday value: the argument  person("Per", "Schultze") and the argument date("Apr", 14, 1960). The functors of these values are  person and date.
==Унификация Составных Доменов==
 
Значения составных доменов могут унифицироваться либо с одиночными переменными, либо с составными значениями, которые сопоставимы с ними (здесь возможны переменные, как части внутренней структуры). Это значин, что Вы можете использовать составные величины для передачи в виде целого набора данных, а затем разбирать их по частям путем операции унификации. К примеру,
==Unification of Compound Domains==
 
A compound value can unify either with a simple variable or with a compound value that matches it(perhaps containing variables as parts of its internal structure). This means you can use a compound value to pass the whole collection of items as a single value, and then use unification to pick them apart. For example,
 
<vip>date("April", 14, 1960)</vip>
<vip>date("April", 14, 1960)</vip>
 
сопоставляется с переменной X и связывает переменную X с date("April",14,1960). Кроме того,
matches X and binds X to date("April",14,1960). Also
 
<vip>date("April", 14, 1960)</vip>
<vip>date("April", 14, 1960)</vip>
сопоставляется с date(Mo,Da,Yr) и связывает Mo с "April", Da с 14 и Yr с 1960.
===Использование Знака Равенства для Унификации Составных Доменов===
Visual Prolog выполняет унификацию в двух случаях. Первый - это когда вызов сопоставляется с головой клаузы. Второй - это при использовании знака '''''равно''''' (=), который в действительности является инфиксной формой предиката (предикат, который находится '''между''' своими аргументами, вместо того, чтобы быть '''перед''' ними).


matches date(Mo,Da,Yr) and binds Mo to "April", Da to 14, and Yr to 1960.
Visual Prolog осуществляет необходимые связывания для унификации значений по обе стороны от знака '''равно'''. Это полезно для выделения значений аргументов в составных величинах. Например, следующий код проверяет имеют ли два человека одинаковы фамилии, после чего второй человек получает тот же адрес, что и первый.
 
===Using the Equal Sign to Unify Compound Domains===
 
Visual Prolog performs unification in two places. The first is when a call or goal matches the head of a clause. The second is across the '''''equal'''''(=) sign, which is actually an infix predicate(a predicate that is located ''between'' its arguments rather than ''before'' them).
 
Visual Prolog will make the necessary bindings to unify the values on both sides of the '''''equal''''' sign. This is useful for finding the values of arguments within a compound value. For example, the following code excerpt tests if two people have the same last name, then gives the second person the same address as the first.
 
<vip>class my
<vip>class my
     domains
     domains
Строка 85: Строка 72:
         run():-
         run():-
             console::init(),
             console::init(),
             P1 =  person(name("Jim", "Smith"),
             P1 =  person(name("Jim", "Smith"),addr(street(5, "1st st"), "igo", "CA")),
                addr(street(5, "1st st"), "igo", "CA")),
             P1 = person(name(_, "Smith"), Address),
             P1 = person(name(_, "Smith"), Address),
             P2 = person(name("Jane", "Smith"), Address),
             P2 = person(name("Jane", "Smith"), Address),
Строка 99: Строка 85:
     my::run.</vip>
     my::run.</vip>


==Treating Several Items as One==
==Структура Данных Как Единое Целое==
 
Составные данные могут рассматриваться как одиночные значения в клаузах Пролога, что значительно упрощает программирование. Рассмотрим следующий факт
Compound values can be regarded and treated as single values in your Prolog clauses, which greatly simplifies programming. Consider, for example, the fact
 
<vip>owns("John", book("From Here to Eternity", "James Jones")).</vip>
<vip>owns("John", book("From Here to Eternity", "James Jones")).</vip>
 
в котором утверждается, что John владеет книгой "From Here to Eternity", написанной автором "James Jones". В то же время можно написать
in which you state that John owns the book "From Here to Eternity", written by "James Jones". Likewise, you could write
 
<vip>owns("John", horse("Blacky")).</vip>
<vip>owns("John", horse("Blacky")).</vip>
что может интерпретироваться как
John владеет лошадью с именем Blacky.


which can be interpreted as
Составными значениями в этих двух примерах являются
 
John owns a horse named Blacky.
 
The compound values in these two examples are
 
<vip>book("From Here to Eternity", "James Jones")).</vip>
<vip>book("From Here to Eternity", "James Jones")).</vip>
 
и
and
 
<vip>horse("Blacky").</vip>
<vip>horse("Blacky").</vip>
 
Если бы вместо этого было бы записаны два факта:
If you had instead written two facts:
 
<vip>owns("John", "From Here to Eternity").
<vip>owns("John", "From Here to Eternity").
owns("John", "Blacky").</vip>
owns("John", "Blacky").</vip>
то нельзя было бы решить является ли Blacky названием книги или имнем лошади. С другой стороны, можно использовать первый компонент структуры – функтор, для обозначения различия между различными типами данных. В этом примере использовались, соответственно функторы book и horse для того, чтобы показать различие между данными.


you would not have been able to decide whether Blacky was the title of a book or the name of a horse. On the other hand, you can use the first component of a compound value – the functor – to distinguish between different kinds of values. This example used the functors book and horse to indicate the difference between the values.
'''Помните''': Составные данные состоят из функтора и аргументов, принадлежащих функтору, как показано ниже:
 
'''Remember''': Compound values consist of a functor and the arguments belonging to that functor, as follows:
 
<vip>functor(argument1, argument2, ..., argumentN)</vip>
<vip>functor(argument1, argument2, ..., argumentN)</vip>


===An Example Using Compound Domains===
===Пример Использования Составных Доменов===
 
Важным свойством составных доменов является возможность легкой передачи сгруппированных данных с помощью одного аргумента. Рассмотрим случай поддержки базы данных телефонных номеров. В эту базу данных мы хотим включать также дни рождения своих друзей и членов их семей. Ниже приведен фрагмент кода, который можно было бы написать для этого:
An important feature of compound domains allows you to easily pass a group of values as one argument. Consider a case where you are keeping a telephone database. In your database, you want to include your friends' and family members' birthdays. Here is a section of code you might have come up with:


<vip>predicates
<vip>predicates
Строка 143: Строка 115:
     phone_list("Chris", "Grahm", "433-9906", "may", 12, 1962).</vip>
     phone_list("Chris", "Grahm", "433-9906", "may", 12, 1962).</vip>


Examine the data, noticing the six arguments in the fact phone_list; five of these arguments can be broken down into two compound domains, like this:
Анализируя данные, замечаем, что факт phone_list имеет шесть аргументов; пять из них могут разбиты на два составных домена:


<pre>      person                birthday
<pre>      person                birthday
         /  \                /  |  \
         /  \                /  |  \
       /    \              /  |  \
       /    \              /  |  \
First Name  Last Name    Month Day Year</pre>It might be more useful to represent your facts so that they reflect these compound domains. Going back a step, you can see that person is a relationship, and the first and last names are the arguments. Also, birthday is a relationship with three arguments: month, day, and year. The Prolog representation of these relationships is
First Name  Last Name    Month Day Year</pre>
 
Было бы более полезно представить эти факты в виде составных доменов. Отступив на шаг, видим, что персона (person) является отношением, а имя (First Name) и фамилия (Last Name) являются его аргументами. Кроме того, день рождения является также отношением с тремя аргументами: месяц (month), день (day) и год (year). Представление этих отношений средствами Пролога аналогично уже упоминавшемуся
<vip>owns("John", "From Here to Eternity").
<vip>owns("John", "From Here to Eternity").
owns("John", "Blacky").</vip>
owns("John", "Blacky").</vip>
 
Теперь можно переписать нашу небольшую базу данных с использованием этих составных доменов в базе данных.
You can now rewrite your small database to include these compound domains as part of your database.


<vip>domains
<vip>domains
Строка 162: Строка 133:
     phone_list :(name, string Ph_num, birthday) determ.
     phone_list :(name, string Ph_num, birthday) determ.
clauses
clauses
     phone_list(person("Ed", "Willis"), "422-0208",
     phone_list(person("Ed", "Willis"), "422-0208",b_date("aug", 3, 1955)).
          b_date("aug", 3, 1955)).
     phone_list(person("Chris", "Grahm"), "433-9906",b_date("may", 12, 1962)).</vip>
     phone_list(person("Chris", "Grahm"), "433-9906",
          b_date("may", 12, 1962)).</vip>


In this program, two compound '''domains''' declarations were introduced. We go into more detail about these compound data structures later in this chapter. For now, we will concentrate on the benefits of using such compound domains.
В этой программе появились объявления двух составных '''доменов (domains)'''. Мы детальнее рассмотрим эти составные структыры данных позднее в этой главе. Пока же мы обратим внимание на преимущества использования таких составных доменов.


The phone_list predicate now contains three arguments, as opposed to the previous six. Sometimes breaking up your data into compound values will clarify your program and might help process the data.
Предикат phone_list теперь содержит три аргумента вместо прежних шести. Иногда разбиение данных на составные структуры упрощает программу и облегчает обработку данных.
 
Now add some rules to program. Suppose you want to create a list of people whose birthdays are in the current month. Here is the program code to accomplish this task; this program uses the standard predicate date to get the current date from the computer's internal clock. Predicate date will return the current year, month, and day from your computer's clock.


Теперь добавим некоторые правила в программу. Предположим, нам надо получить список людей, чьи дни рождения выпадают на текущий месяц. Ниже приведена программа, решающая эту задачу; эта программа использует стандартный предикат date для получения текущей даты из внутренних часов компьютера. Предикат date возвращает текущий год (year), месяц (month) и день (day) часов компьютера.
<vip>class my
<vip>class my
    domains
domains
        name = person(string First, string Last).
  name = person(string First, string Last).
        birthday = b_date(string Month, integer Day, integer Year).
  birthday = b_date(string Month, integer Day, integer Year).
    predicates
predicates
        phone_list :(name, string Ph_num, birthday) multi(o,o,o).
  phone_list :(name, string Ph_num, birthday) multi(o,o,o).
        get_months_birthdays :().
  get_months_birthdays :().
        convert_month :(string Name, integer Num) determ(i,o).
  convert_month :(string Name, integer Num) determ(i,o).
        check_birthday_month :(integer Month_num, birthday) determ.
  check_birthday_month :(integer Month_num, birthday) determ.
        write_person :(name).
  write_person :(name).
end class
end class


implement my
implement my
    clauses
        get_months_birthdays() :-
            stdio::write("****** This Month's Birthday List *****\n"),
            stdio::write(" First Name\t\t Last Name\n"),
            stdio::write("***********************************\n"),
            CurTime = time::new(),
            CurTime:getDate(_, ThisMonth, _),
            phone_list(Person, _, Date),
            check_birthday_month(ThisMonth, Date),
            write_person(Person),
            fail.
        get_months_birthdays() :-
            stdio::write("\n Press any key to continue:\n"),
            _ = console::readChar().


     clauses
clauses
        write_person(person(FirstName, LastName)) :-
  get_months_birthdays() :-
            stdio::write("  ", FirstName, "\t\t ",  LastName, "\n").
    stdio::write("****** This Month's Birthday List *****\n"),
    stdio::write(" First Name\t\t Last Name\n"),
    stdio::write("***********************************\n"),
    CurTime = time::new(),
    CurTime:getDate(_, ThisMonth, _),
     phone_list(Person, _, Date),
      check_birthday_month(ThisMonth, Date),
      write_person(Person),
    fail.
  get_months_birthdays() :-
    stdio::write("\n Press any key to continue:\n"),
    _ = console::readChar().
 
clauses
  write_person(person(FirstName, LastName)) :-
    stdio::write("  ", FirstName, "\t\t ",  LastName, "\n").


    clauses
clauses
        check_birthday_month(Mon, b_date(Month, _, _)) :-
  check_birthday_month(Mon, b_date(Month, _, _)) :-
            convert_month(Month, Month1),
    convert_month(Month, Month1),
            Mon = Month1.
    Mon = Month1.


    clauses
clauses
        phone_list(person("Ed", "Willis"), "11-1111", b_date("Jan", 3, 1955)).
  phone_list(person("Ed", "Willis"), "11-1111", b_date("Jan", 3, 1955)).
        phone_list(person("Benjamin", "Thomas"), "222-2222", b_date("Feb", 5, 1965)).
  phone_list(person("Benjamin", "Thomas"), "222-2222", b_date("Feb", 5, 1965)).
        phone_list(person("Ray", "William"), "333-3333", b_date("Mar", 3, 1955)).
  phone_list(person("Ray", "William"), "333-3333", b_date("Mar", 3, 1955)).
        phone_list(person("Tomas", "Alfred"), "444-4444", b_date("Apr", 29, 1975)).
  phone_list(person("Tomas", "Alfred"), "444-4444", b_date("Apr", 29, 1975)).
        phone_list(person("Chris", "Gralm"), "555-5555", b_date("May", 12, 1975)).
  phone_list(person("Chris", "Gralm"), "555-5555", b_date("May", 12, 1975)).
        phone_list(person("Dastin", "Robert"), "666-6666", b_date("Jun", 17, 1975)).
  phone_list(person("Dastin", "Robert"), "666-6666", b_date("Jun", 17, 1975)).
        phone_list(person("Anna", "Friend"), "777-7777", b_date("Jul", 2, 1975)).
  phone_list(person("Anna", "Friend"), "777-7777", b_date("Jul", 2, 1975)).
        phone_list(person("Naomi", "Friend"), "888-8888", b_date("Aug", 10, 1975)).
  phone_list(person("Naomi", "Friend"), "888-8888", b_date("Aug", 10, 1975)).
        phone_list(person("Christina", "Lynn"), "999-9999", b_date("Sep", 25, 1975)).
  phone_list(person("Christina", "Lynn"), "999-9999", b_date("Sep", 25, 1975)).
        phone_list(person("Kathy", "Ann"), "110-1010", b_date("Oct", 20, 1975)).
  phone_list(person("Kathy", "Ann"), "110-1010", b_date("Oct", 20, 1975)).
        phone_list(person("Elizabeth", "Ann"), "110-1111", b_date("Nov", 9, 1975)).
  phone_list(person("Elizabeth", "Ann"), "110-1111", b_date("Nov", 9, 1975)).
        phone_list(person("Aaron", "Friend"), "110-1212", b_date("Dec", 31, 1975)).
  phone_list(person("Aaron", "Friend"), "110-1212", b_date("Dec", 31, 1975)).
        phone_list(person("Jenifer", "Faitlin"), "888-8888", b_date("Aug", 14, 1975)).
  phone_list(person("Jenifer", "Faitlin"), "888-8888", b_date("Aug", 14, 1975)).


    clauses
clauses
        convert_month("Jan", 1).
  convert_month("Jan", 1).
        convert_month("Feb", 2).
  convert_month("Feb", 2).
        convert_month("Mar", 3).
  convert_month("Mar", 3).
        convert_month("Apr", 4).
  convert_month("Apr", 4).
        convert_month("May", 5).
  convert_month("May", 5).
        convert_month("Jun", 6).
  convert_month("Jun", 6).
        convert_month("Jul", 7).
  convert_month("Jul", 7).
        convert_month("Aug", 8).
  convert_month("Aug", 8).
        convert_month("Sep", 9).
  convert_month("Sep", 9).
        convert_month("Oct", 10).
  convert_month("Oct", 10).
        convert_month("Nov", 11).
  convert_month("Nov", 11).
        convert_month("Dec", 12).
  convert_month("Dec", 12).
end implement
end implement


goal
goal
    console::init(),
  console::init(),
    my::get_months_birthdays().</vip>
  my::get_months_birthdays().</vip>
 
How do compound domains help in this program? This should be easy to see when you examine the code. Most of the processing goes on in the get_months_birthdays predicate.
 
*First, the program makes a window to display the results.
 
*After this, it writes a header in the window to help interpret the results.
 
*Next, in get_months_birthdays, the program uses the built-in predicate date to obtain the current month.
 
*After this, the program is all set to search the database and list the people who were born in the current month. The first thing to do is find the first person in the database. The call phone_list(Person, _, Date) binds the person's first and last names to the variable Person by binding the entire functor person to Person. It also binds the person's birthday to the variable Date.<br />
Notice that you only need to use one variable to store a person's complete name, and one variable to hold the birthday. This is the power of using compound domains.
 
*Your program can now pass around a person's birthday simply by passing on the variable Date. This happens in the next subgoal, where the program passes the current month(represented by an integer) and the birthday(of the person it is processing) to the predicate check_birthday_month.
 
*Look closely at what happens. Visual Prolog calls the predicate check_birthday_month with two variables: The first variable is bound to an integer, and the second is bound to a birthday term. In the head of the rule that defines check_birthday_month, the first argument, This_month, is matched with the variable Mon. The second argument, Date, is matched against b_date(Month, _,_).<br />
Since all you are concerned with is the month of a person's birthday, you have used the anonymous variable for both the day and the year of birth.
 
*The predicate check_birthday_month first converts the string for the month into an integer value. Once this is done, Visual Prolog can compare the value of the current month with the value of the person's birthday month. If this comparison succeeds, then the subgoal check_birthday_month succeeds, and processing can continue. If the comparison fails(the person currently being processed was not born in the current month), Visual Prolog begins to backtrack to look for another solution to the problem.


*The next subgoal to process is write_person. The person currently being processed has a birthday this month, so it is OK to print that person's name in the report. After printing the information, the clause fails, which forces backtracking.
Каким образом составные домены помогают в этой программе? Это несложно увидеть, просмотрев код. Основная часть обработки производится в предикате get_months_birthdays.


*'''''Backtracking always goes up to the most recent non-deterministic call and tries to re-satisfy that call.''''' In this program, the last non-deterministic call processed is the call to phone_list. It is here that the program looks up another person to be processed. If there are no more people in the database to process, the current clause fails; Visual Prolog then attempts to satisfy this call by looking further down in the database. Since there is another clause that defines get_months_birthdays, Visual Prolog tries to satisfy the call to get_months_birthdays by satisfying the subgoals to this other clause.
*Сначала создается окно предикатом console::init()
*После этого пишется заголовок в окне, помогающий интерпретировать результаты.
*В предикате get_months_birthdays программа использует встроенный (built-in) предикат date, чтобы получить значение текущего месяца.
*Далее все в программе направлено на поиск в базе данных и получения списка людей, родившихся в текущем месяце. Прежде всего надо найти первого человека в базе данных. Вызов phone_list(Person, _, Date) связывает имя и фамилию человка с переменной Person путем связывания целого функтора person с этой переменной Person. Одновремиенно связывается день рождения человека с переменной Date.<br/> Обратите внимание, что для этого требуется всего одна переменная для запоминания полного имени человека и всего одна переменная Date для запоминания дня рождения. В этом и заключается мощность составных доменов.
*Теперь программа передает день рождения человека в виде одной переменной Date. Это происходит в следующей подцели, где программа передает значение текущего месяца (month), представленного целым числом, и день рождения (обрабатываемого человека) в предикат check_birthday_month.
*Посмотрите внимательно, что происходит. Visual Prolog вызывает предикат check_birthday_month с двуми параметрами: первый параметр связан с целым числом, а второй -  с термом birthday. В голове правила, определяющего предикат check_birthday_month, первый аргумент сопоставляется с переменной Mon. Второй аргумент, Date, сопоставляется с b_date(Month, _,_).<br/> Поскольку нас интересует только месяц рождения человека, то на месте как дня, так и года рождения мы используем анонимную переменную.
*Предикат check_birthday_month сначала преобразует строку с названием месяца в целочисленное значение. Как только это сделано, Visual Prolog может сравнивать значение текущего месяца со значением месяца рождения человека. Если такое сравнение положительно (успешно), то цель check_birthday_month считается успешной, и обработка продолжается. Если же сравнение неуспешно (fails) - обрабатываемый человек не родился в текущем месяце, то Visual Prolog начинает операцию отката для поиска другого возможного решения задачи.
*Следующая обрабатываемая подцель - write_person. У обрабатываемого человека день рождения выпадает на текущий месяц, следовательно можно печатать его имя в отчете. После печати информации, клауза завершается предикатом неуспешности (fail), что вызывает откат.
*'''''Откат всегда распространяется вверх до ближайшего недетерминированного вызова и пытается передоказать вызов.''''' В нашей программе последним недетерминированным вызовом является вызов phone_list. Именно здесь программа выбирает следующего человека для обработки. Если людей в списке (в базе данных) больше нет, то текущая клауза завершается неуспешно (fails); Visual Prolog теперь пытается доказать эту цель путем просмотра быза правил ниже. Поскольку есть еще одна клауза, определяющая get_months_birthdays, Visual Prolog пытается доказать обращение к get_months_birthdays путем доказательства подцелей этой другой части клаузы.


==Declaring Compound Domains==
=Декларирование Составных Доменов=


In this section, we show you how instances of compound domains are defined. After compiling a program that contains the following relationships:
В этом разделе мы покажем как определяются составные домены. После компиляции программы, содержащей следующие отношения:


<vip>owns("John", book("From Here to Eternity", "James Jones")).</vip>
<vip>owns("John", book("From Here to Eternity", "James Jones")).</vip>
 
и
and
 
<vip>owns("John", horse("Blacky")).</vip>
<vip>owns("John", horse("Blacky")).</vip>
 
можно запрашивать систему с помощью следующей цели:
you could query the system with this goal:
<vip>owns("John", X)</vip>
 
Переменная X может быть связана с различными типами данных - книгой, лошадью или возможно другим типом, который будет определен. Поскольку предикат owns теперь определён, больше его нельзя использовать в смысле старого определения:
<vip>owns("John", X))</vip>
 
The variable X can be bound to a different type: a book, a horse, or perhaps other types you define. Because of your definition of the owns predicate, you can no longer employ the old predicate declaration of owns:
 
<vip>owns :(string, string).</vip>
<vip>owns :(string, string).</vip>
 
Второй аргумент больше не относится к домену string. Вместо этого необходимо сформулировать новое определение предиката, такое как, например
The second argument no longer refers to the string domain. Instead, you must formulate a new declaration to the predicate, such as
 
<vip>owns :(name, articles).</vip>
<vip>owns :(name, articles).</vip>
 
Вы можете теперь описать домен articles в секции domains как показано здесь:
You can describe the articles domain in the domains section as shown here:
 
<vip>domains
<vip>domains
    articles = book(string Title, string Author);
  articles =  
            horse(string Name).
    book(string Title, string Author);
        /* Articles are books or horses */</vip>
    horse(string Name).
 
/* Articles - это книги или лошади */</vip>
The semicolon is read as ''or''. In this case, two alternatives are possible: A book can be identified by its title and author, or a horse can be identified by its name. The domains title, author, and name are all of the standard domain string.
Точка с запятой здесь читаются как ''or (или)''. В нашем случае возможны две альтернативы: либо книга может быть определена своим названием и автором, либо лошадь определяется своим именем. Домены Title, Author и Name - все являются стандартными доменами string.
 
More alternatives can easily be added to the domains declaration. For example, articles could also include a boat, a house, or a bankbook. For a boat, you can make do with a functor that has no arguments attached to it. On the other hand, you might want to give a bank balance as a figure within the bankbook. The domains declaration of  articles is therefore extended to:


Дополнительные альтернативы могут быть легко добавлены в декларацию домена. К примеру, articles мог бы включать лодку, дом, или банковский счёт. Для лодки, можно было бы использовать функтор без аргументов. С другой стороны, может возникнуть желание представлять баланс счета в качестве банковской записи. Тогда объявления домена articles расширяются до:
<vip>domains
<vip>domains
    articles = book(string Title, string Author);
  articles =  
            horse(string Name); boat; bankbook(real Balance).</vip>
    book(string Title, string Author);
 
    horse(string Name);  
Here is a full program that shows how compound domains from the domain articles can be used in facts that define the predicate owns.
    boat;  
 
    bankbook(real Balance).</vip>
Далее приведена полная программа, которая показывает как составной домен articles может быть использован в определениях предиката owns.
<vip>class my
<vip>class my
    domains
domains
        articles =
  articles =
            book(string Title, string Author) ;
    book(string Title, string Author) ;
            horse(string Name) ; boat ; bankbook(real Balance).
    horse(string Name) ;  
    predicates
    boat ;  
        owns :(string Name, articles) nondeterm(i,o) determ(i,i).
    bankbook(real Balance).
predicates
  owns :(string Name, articles) nondeterm(i,o) determ(i,i).
end class
end class


implement my
implement my
    clauses
  clauses
        owns("John", book("A friend of the family", "Irwin Shaw")).
    owns("John", book("A friend of the family", "Irwin Shaw")).
        owns("John", horse("Blacky")).
    owns("John", horse("Blacky")).
        owns("John", boat).
    owns("John", boat).
        owns("John", bankbook(1000)).
    owns("John", bankbook(1000)).
end implement
end implement


goal
goal
    console::init(),
  console::init(),
    my::owns("John", Thing),
  my::owns("John", Thing),
     stdio::write("Thing: ", Thing, "\n"),
     stdio::write("Thing: ", Thing, "\n"),
    fail
  fail
    ;
  ;
    stdio::write("The end.").</vip>
  stdio::write("The end.").
 
</vip>
Now load the program into Visual Development Environment and run it in window. Visual Prolog responds with:
Загрузим теперь эту программу в IDE и запустим её. Visual Prolog теперь ответит:
 
<vip>Thing: book("A friend of the family","Irwin Shaw")
<vip>Thing: book("A friend of the family","Irwin Shaw")
Thing: horse("Blacky")
Thing: horse("Blacky")
Строка 338: Строка 288:
The end.</vip>
The end.</vip>


===Writing Domain Declarations: a Summary===
==Итак: правила объявлений==
 
This is a generic representation of how to write compound domain declarations:
 
<vip>domain = alternative1(D, D, ...);
              alternative2(D, D, ...);
...</vip>
 
Here, alternative1 and  alternative2 are arbitrary(but different) functors. The notation(D, D, ...) represents a list of domain names that are either declared elsewhere or are one of the standard domain types(such as string, integer, real, etc).
 
===='''Note''':====
 
*The alternatives are separated by semicolons.
 
*Every alternative consists of a functor and, possibly, a list of domains for the corresponding arguments.


*If the functor has no arguments, you can write it as alternativeN or alternativeN( ) in your programs.
Дадим теперь общее представление способа записи объявлений составных доменов:
<vip>
domain =
  alternative1(D, D, ...);
  alternative2(D, D, ...);
  ...
</vip>
Здесь alternative1 и alternative2 являются равновозможными (но различными) функторами. Обозначение (D, D, ...) представляет список имен, которые объявлены в любом месте либо относятся к стандартным типам доменов(таким как string, integer, real и т.д.).


===Multi-Level Compound Domains===
'''Примечания:'''
*Алтернативы разделяются точкой с запятой.
*Каждая альтернатива состоит из фуктора и, возможно, списка доменов для соответствующих аргументов.
*Если функтор не имеет аргументов, его можно записывать как alternativeN или alternativeN( ).


Visual Prolog allows you to construct compound domains on several levels. For example, in
==Многоуровневые Составные Домены==
Visual Prolog позволяет организовывать многоуровневые составные домены. Например, в


<vip>book("The Ugly Duckling", "Andersen").</vip>
<vip>book("The Ugly Duckling", "Andersen").</vip>


instead of using the author's last name, you could use a new structure that describes the author in more detail, including both the author's first and last names. By calling the functor for the resulting new compound domain author, you can change the description of the book to
вместо использования фамилии автора, можно было бы использовать новую структуру, детализирующую автора глубже, включая имя и фамилию автора. Дав название новому составному домену author, теперь можно поменять описание книги на


<vip>book("The Ugly Duckling", author("Hans Christian", "Andersen")).</vip>
<vip>book("The Ugly Duckling", author("Hans Christian", "Andersen")).</vip>


In the old domain declaration
В старом объявлении домена


<vip>book(string Title, string Author).</vip>
<vip>book(string Title, string Author).</vip>


the second argument of the book functor is Author that can only include a single name, so it is no longer sufficient. You must now specify that an author is also a compound domain made up of the author's first and last name. You do this with the domain definition:
второй аргумент функтора book, представляющий автора может представлять только одиночное имя, но этого теперь недостаточно. Надо теперь объявить, что автор представляется составным доменом, образованным из имени и фамилии. Это делается декларацией домена:


<vip>author = author(string First_name, string Last_name).</vip>
<vip>author = author(string First_name, string Last_name).</vip>


which leads to the following declarations:
что приводит к следующим объявлениям:


<vip>domains
<vip>domains
     articles = book(string Title, string Author); ...
     articles = book(string Title, author Author); ...
         /* First level */
         /* First level */
     author = author(string First_name, string Last_name).
     author = author(string First_name, string Last_name).
         /* Second level */</vip>
         /* Second level */</vip>


When using compound domains on different levels in this way, it is often helpful to draw a "tree":
Используя составные домены таким образом на разных уровнях, часто бывает полезно рисовать дерево:


<pre>      book
<pre>      book
Строка 390: Строка 337:
           /  \
           /  \
           /    \
           /    \
   First_name  Last_name</pre>A domain declaration describes only one level of the tree at a time, and not the whole tree. For instance, a book can't be defined with the following domain declaration:
   First_name  Last_name</pre>
 
Одна декларация домена всегда описывает только один уровень дерева, а не все дерево. В частности, домен book не может быть объявлен  такой декларацией:


<vip>/* Not allowed */
<vip>/* Not allowed */
book = book(string Title, author(string First_name, string Last_name)).</vip>
book = book(string Title, author(string First_name, string Last_name)).</vip>


==Compound Mixed-Domain Declarations==
=Объявления Составных Смешанных Доменов=
 
In this section, we discuss three different types of domain declarations you can add to your programs. These declarations allow you to use predicates that
 
*take an argument, more than one type of more than one possible type


*take a variable number of arguments, each of a specified type
Давайте обсудим три различных объявления доменов, которые можно включать в программы. Эти декларации позволяют использовать предикаты, которые


*take a variable number of arguments, some of which might be of more than one possible type
*принимают аргументы более чем одного типа
*принимают переменное число аргументов одного типа
*принимают переменное число аргументов, каждый из которых может иметь более одного типа


===Multiple-Type Arguments===
==Аргументы множественных типов==


To allow a Visual Prolog predicate to accept an argument that gives information of different types, you must add a functor declaration. In the following example, the your_age clause will accept an argument of type age, which can be a string, a real, or an integer.
Для того, чтобы предикат в Visual Prolog допускал использование аргументов различного типа, необходимо использовать функторные объявления. Следующий пример, клауза your_age clause будет воспринимать аргументы типа age, которые могут иметь тип string, real или integer.


<vip>domains
<vip>domains
Строка 419: Строка 366:
     your_age(s(Age)) :- stdio::write(Age).</vip>
     your_age(s(Age)) :- stdio::write(Age).</vip>


Visual Prolog does not allow the following domain declaration:
При этом Visual Prolog не допускает декларации вида:


<vip>/* Not permitted. */
<vip>/* Не допустимо. */
domains
domains
     age = integer; real; string.</vip>
     age = integer; real; string.</vip>


===Lists===
==Списки==


Suppose you are keeping track of the different classes a professor might teach. You might produce the following code:
Предположим, мы обрабатываем данные о предметах, которые может преподавать учитель. Код может выглядеть так:


<vip>class predicates
<vip>class predicates
Строка 440: Строка 387:
     teacher("Chris", "Grahm", "geometry").</vip>
     teacher("Chris", "Grahm", "geometry").</vip>


Here, you need to repeat the teacher's name for each class he or she teaches. For each class, you need to add another fact to the database. Although this is perfectly OK in this situation, you might find a school where there are hundreds of classes; this type of data structure would get a little tedious. Here, it would be helpful if you could create an argument to a predicate that could take on one ''or more'' values.
Здесь необходимо повторять имя преподавателя для каждого предмета, который он (она) может преподавать. Для каждого предмета необходимо добавлять такой факт в базу данных. Хотя в данной ситуации это не вызывает трудности, можно найти школу, в которой сотни предметов, и тогда поддержка такой структур данных становится утомительной. Тогда было бы удобно создать такой аргумент для предиката, который мог бы воспринимать один или несколько значений.


A list in Prolog does just that. In the following code, the argument class is declared to be of a list type. We show here how a list is represented in Prolog.
В Прологе это делает '''список'''. В следующем коде аргумент class объявлен имеющим '''тип список'''. Покажем, как список представляется в Прологе.


<vip>domains
<vip>domains
     classes = string*.  /* declare a list domain */
     classes = string*.  /* объявляет списковый домен*/


class predicates
class predicates
Строка 454: Строка 401:
     teacher("Chris", "Grahm", ["geometry"]).</vip>
     teacher("Chris", "Grahm", ["geometry"]).</vip>


In this example, the code is more concise and easier to read than in the preceding one. Notice the domains declaration. The asterisk(*) means that classes is a list of strings. You can just as easily declare a list of integers:
В этом примере код более лаконичен и проще читается, чем код предыдущего примера. Обратите внимание на объявление домена. Звездочка (*) означает, домен classes является списком строк. Так же легко можно объявить список целых:


<vip>domains
<vip>domains
     integer_list = integer*.</vip>
     integer_list = integer*.</vip>


Once you declare a domain, it is easy to use it; just place it as an argument to a predicate declared in the predicates section. Here is an example of using an integer list:
Объявив домен, теперь просто его использовать, поместив его в качестве аргумента в декларации предиката в секции предикатов. Посмотрим пример использования списка целых:


<vip>domains
<vip>domains
Строка 475: Строка 422:
     test_scores("Jeff", "Zheutlin", []).</vip>
     test_scores("Jeff", "Zheutlin", []).</vip>


In the case of ''Jeff Zheutlin'', notice that a list doesn't need to contain any elements at all.
Обратите внимание, в случае ''Jeff Zheutlin'' список не содержит никаких элементов.


One more example shows how one can use lists to describe the family-tree.
Еще один пример показывает как можно использовать списки для представления семейного дерева.


<vip>domains
<vip>domains
Строка 495: Строка 442:
         )).</vip>
         )).</vip>


==Summary==
=Заключение=
 
These are the important points covered in this tutorial:
 
*A Visual Prolog program can contain many types of values: simple and compound, standard and user-defined.
 
*Simple values are numbers, strings, etc.
 
*''Compound domains'' allow you to treat several pieces of information as a single item. A compound domain consists of a name(known as a functor) and one or more arguments. You can define a domain with several alternative functors.
 
*A functor in Visual Prolog is not the same thing as a function in other programming languages. '''''A functor does not stand for some computation to be performed.''''' It is just a name that identifies a kind of a compound domain and holds its arguments together.
 
*Compound values can be regarded and treated as single values; you use the functor to distinguish between different kinds of compound values. Visual Prolog allows you to construct compound values on several levels; the arguments of a compound value can also be compound values. With compound mixed domain declarations, you can use predicates that:
 
*take an argument of more than one possible type(functor declaration).


*take a variable number of arguments, each of a specified type(list declaration).
В этом руководстве были рассмотрены важные понятия:


*take a variable number of arguments, some of which might be of more than one possible type.
*Программа на Visual Prolog может содержать множество типов значений: простых и составных, встроенных (стандартных) и определяемых в программе.
*Простые значения - это: числа, строки и т.д..
*''Составные домены'' позволяют рассматривать структуру информации как единую сущность. Составной домен состоит из имени (известного как функтор) и, возможно, аргументов. Можно определить домен с несколькими альтернативными функторами.
*Функтор в Visual Prolog не есть то же, что функция в других языках программирования. '''''Функтор не вызывает никаких вычислений.''''' Это просто имя которое обозначает  составной домен и объединяет его аргументы.
*Составные значения могут обрабатываться и рассматриваться как одиночные переменные; функторы используются для создания различия между различными типами составных значений. Visual Prolog позволяет строить многоуровневые структурированные значения; Аргументами составных доменов могут быть опять-таки составные значения. Используя декларации составных смешанных доменов, можно использовать предикаты, которые:
**принимают аргументы нескольких различных типов (определяется объявлениями функторов).
**принимают переменное число аргументов, каждый определённого типа (объявления списков).
**принимают переменное число аргументов, часть из которых могут иметь более одного типа.


==References==
=Ссылки=
[[en:Compound and List Domains]]
[[en:Compound and List Domains]]
[[Категория:VipРуководства]]
[[Категория:VipРуководства]]
[[Категория:VipLanguage]]

Текущая версия на 08:16, 6 ноября 2007

В этом руководстве мы представим составные домены (иногда называемые алгебраическими структурами данных). Составные домены используются для обработки наборов данных как единого целого. Списки являются примером составных доменов. Они используются настолько часто, что получили даже специальную синтаксическую окраску.

Составные домены и списки создаются с использованием встроенных (built-in) и других составных или списковых доменов. Справочная система Visual Prolog (Help) объясняет встроенные домены:

  • integer - целые
  • real - вещественные
  • string - строковые
  • symbol -символьные
  • char - знаковые
  • sring8 - строковые 8-разрядные
  • pointer - указатели
  • binary - двоичные (бинарные)
  • boolean - булевские
  • object - объекты

Составные домены и Функторы

Составные домены позволяют рассматривать наборы данных как единое целое и при этом мы можете рассматривать их и раздельно. Рассмотрим, например, дату "October 15, 2003". Она состоит из трех единиц информации – месяца, дня и года – но было бы полезно рассматривать дату как единое целое в виде древовидной структуры:

        DATE
         /|\
        / | \
       /  |  \
      /   |   \
  October 15  2003

Это можно сделать путем объявления домена date_cmp, содержащего данные date:

domains
    date_cmp = date(string Month, unsigned Day, unsigned Year).

и затем писать просто, то есть

D = date("October", 15, 2003),

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

Обращаем Ваше внимание на то, что функтор в Visual Prolog не имеет ничего общего с функциями в языках программирования. Функтор не вызывает никаких вычислений, это всего лишь имя, которое идентифицирует составную величину и объединяет свои аргументы.

Аргументы составной величины могут быть, в свою очередь, составными. К примеру, Вы можете думать о чьем-либо дне рождения как о структуре данных, так, как показано:

            birthday
             /    \
            /      \
           /        \
          /          \
         /            \
   person             date
    /  \              / | \
   /    \            /  |  \
"Per" "Schultze"  "Apr" 14 1960

На Прологе это может быть записано как:

birthday(person("Per", "Schultze"), date("Apr",14,1960)).

В этом примере видны две подчасти составного значения birthday: аргумент person("Per", "Schultze") и аргумент date("Apr", 14, 1960). Функторами этих величин являются 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.

Использование Знака Равенства для Унификации Составных Доменов

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

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

class my
    domains
        person = person(name, address).
        name = name(string First, string Last).
        address = addr(street, string City, string State).
        street = street(integer Number, string Street_name).
 
    predicates
        run :().
end class
 
implement my
    clauses
        run():-
            console::init(),
            P1 =  person(name("Jim", "Smith"),addr(street(5, "1st st"), "igo", "CA")),
            P1 = person(name(_, "Smith"), Address),
            P2 = person(name("Jane", "Smith"), Address),
            !,
            stdio::write("P1 = ", P1, "\n"),
            stdio::write("P2 = ", P2, "\n")
            ;
            stdio::write("No solution").
end implement
 
goal
    my::run.

Структура Данных Как Единое Целое

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

owns("John", book("From Here to Eternity", "James Jones")).

в котором утверждается, что John владеет книгой "From Here to Eternity", написанной автором "James Jones". В то же время можно написать

owns("John", horse("Blacky")).

что может интерпретироваться как John владеет лошадью с именем Blacky.

Составными значениями в этих двух примерах являются

book("From Here to Eternity", "James Jones")).

и

horse("Blacky").

Если бы вместо этого было бы записаны два факта:

owns("John", "From Here to Eternity").
owns("John", "Blacky").

то нельзя было бы решить является ли Blacky названием книги или имнем лошади. С другой стороны, можно использовать первый компонент структуры – функтор, для обозначения различия между различными типами данных. В этом примере использовались, соответственно функторы book и horse для того, чтобы показать различие между данными.

Помните: Составные данные состоят из функтора и аргументов, принадлежащих функтору, как показано ниже:

functor(argument1, argument2, ..., argumentN)

Пример Использования Составных Доменов

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

predicates
    phone_list :(string First, string Last, string Phone, string Month, intege Day, integer Year) determ.
 
clauses
    phone_list("Ed", "Willis", "422-0208", "aug", 3, 1955).
    phone_list("Chris", "Grahm", "433-9906", "may", 12, 1962).

Анализируя данные, замечаем, что факт phone_list имеет шесть аргументов; пять из них могут разбиты на два составных домена:

       person                birthday
        /   \                /  |  \
       /     \              /   |   \
First Name  Last Name    Month Day Year

Было бы более полезно представить эти факты в виде составных доменов. Отступив на шаг, видим, что персона (person) является отношением, а имя (First Name) и фамилия (Last Name) являются его аргументами. Кроме того, день рождения является также отношением с тремя аргументами: месяц (month), день (day) и год (year). Представление этих отношений средствами Пролога аналогично уже упоминавшемуся

owns("John", "From Here to Eternity").
owns("John", "Blacky").

Теперь можно переписать нашу небольшую базу данных с использованием этих составных доменов в базе данных.

domains
    name = person(string First, string Last).
    birthday = b_date(string Month, integer Day, integer Year).
 
class predicates
    phone_list :(name, string Ph_num, birthday) determ.
clauses
    phone_list(person("Ed", "Willis"), "422-0208",b_date("aug", 3, 1955)).
    phone_list(person("Chris", "Grahm"), "433-9906",b_date("may", 12, 1962)).

В этой программе появились объявления двух составных доменов (domains). Мы детальнее рассмотрим эти составные структыры данных позднее в этой главе. Пока же мы обратим внимание на преимущества использования таких составных доменов.

Предикат phone_list теперь содержит три аргумента вместо прежних шести. Иногда разбиение данных на составные структуры упрощает программу и облегчает обработку данных.

Теперь добавим некоторые правила в программу. Предположим, нам надо получить список людей, чьи дни рождения выпадают на текущий месяц. Ниже приведена программа, решающая эту задачу; эта программа использует стандартный предикат date для получения текущей даты из внутренних часов компьютера. Предикат date возвращает текущий год (year), месяц (month) и день (day) часов компьютера.

class my
domains
  name = person(string First, string Last).
  birthday = b_date(string Month, integer Day, integer Year).
predicates
  phone_list :(name, string Ph_num, birthday) multi(o,o,o).
  get_months_birthdays :().
  convert_month :(string Name, integer Num) determ(i,o).
  check_birthday_month :(integer Month_num, birthday) determ.
  write_person :(name).
end class
 
implement my
 
clauses
  get_months_birthdays() :-
    stdio::write("****** This Month's Birthday List *****\n"),
    stdio::write(" First Name\t\t Last Name\n"),
    stdio::write("***********************************\n"),
    CurTime = time::new(),
    CurTime:getDate(_, ThisMonth, _),
    phone_list(Person, _, Date),
      check_birthday_month(ThisMonth, Date),
      write_person(Person),
    fail.
  get_months_birthdays() :-
    stdio::write("\n Press any key to continue:\n"),
    _ = console::readChar().
 
clauses
  write_person(person(FirstName, LastName)) :-
    stdio::write("  ", FirstName, "\t\t ",  LastName, "\n").
 
clauses
  check_birthday_month(Mon, b_date(Month, _, _)) :-
    convert_month(Month, Month1),
    Mon = Month1.
 
clauses
  phone_list(person("Ed", "Willis"), "11-1111", b_date("Jan", 3, 1955)).
  phone_list(person("Benjamin", "Thomas"), "222-2222", b_date("Feb", 5, 1965)).
  phone_list(person("Ray", "William"), "333-3333", b_date("Mar", 3, 1955)).
  phone_list(person("Tomas", "Alfred"), "444-4444", b_date("Apr", 29, 1975)).
  phone_list(person("Chris", "Gralm"), "555-5555", b_date("May", 12, 1975)).
  phone_list(person("Dastin", "Robert"), "666-6666", b_date("Jun", 17, 1975)).
  phone_list(person("Anna", "Friend"), "777-7777", b_date("Jul", 2, 1975)).
  phone_list(person("Naomi", "Friend"), "888-8888", b_date("Aug", 10, 1975)).
  phone_list(person("Christina", "Lynn"), "999-9999", b_date("Sep", 25, 1975)).
  phone_list(person("Kathy", "Ann"), "110-1010", b_date("Oct", 20, 1975)).
  phone_list(person("Elizabeth", "Ann"), "110-1111", b_date("Nov", 9, 1975)).
  phone_list(person("Aaron", "Friend"), "110-1212", b_date("Dec", 31, 1975)).
  phone_list(person("Jenifer", "Faitlin"), "888-8888", b_date("Aug", 14, 1975)).
 
clauses
  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).
end implement
 
goal
  console::init(),
  my::get_months_birthdays().

Каким образом составные домены помогают в этой программе? Это несложно увидеть, просмотрев код. Основная часть обработки производится в предикате get_months_birthdays.

  • Сначала создается окно предикатом console::init()
  • После этого пишется заголовок в окне, помогающий интерпретировать результаты.
  • В предикате get_months_birthdays программа использует встроенный (built-in) предикат date, чтобы получить значение текущего месяца.
  • Далее все в программе направлено на поиск в базе данных и получения списка людей, родившихся в текущем месяце. Прежде всего надо найти первого человека в базе данных. Вызов phone_list(Person, _, Date) связывает имя и фамилию человка с переменной Person путем связывания целого функтора person с этой переменной Person. Одновремиенно связывается день рождения человека с переменной Date.
    Обратите внимание, что для этого требуется всего одна переменная для запоминания полного имени человека и всего одна переменная Date для запоминания дня рождения. В этом и заключается мощность составных доменов.
  • Теперь программа передает день рождения человека в виде одной переменной Date. Это происходит в следующей подцели, где программа передает значение текущего месяца (month), представленного целым числом, и день рождения (обрабатываемого человека) в предикат check_birthday_month.
  • Посмотрите внимательно, что происходит. Visual Prolog вызывает предикат check_birthday_month с двуми параметрами: первый параметр связан с целым числом, а второй - с термом birthday. В голове правила, определяющего предикат check_birthday_month, первый аргумент сопоставляется с переменной Mon. Второй аргумент, Date, сопоставляется с b_date(Month, _,_).
    Поскольку нас интересует только месяц рождения человека, то на месте как дня, так и года рождения мы используем анонимную переменную.
  • Предикат check_birthday_month сначала преобразует строку с названием месяца в целочисленное значение. Как только это сделано, Visual Prolog может сравнивать значение текущего месяца со значением месяца рождения человека. Если такое сравнение положительно (успешно), то цель check_birthday_month считается успешной, и обработка продолжается. Если же сравнение неуспешно (fails) - обрабатываемый человек не родился в текущем месяце, то Visual Prolog начинает операцию отката для поиска другого возможного решения задачи.
  • Следующая обрабатываемая подцель - write_person. У обрабатываемого человека день рождения выпадает на текущий месяц, следовательно можно печатать его имя в отчете. После печати информации, клауза завершается предикатом неуспешности (fail), что вызывает откат.
  • Откат всегда распространяется вверх до ближайшего недетерминированного вызова и пытается передоказать вызов. В нашей программе последним недетерминированным вызовом является вызов phone_list. Именно здесь программа выбирает следующего человека для обработки. Если людей в списке (в базе данных) больше нет, то текущая клауза завершается неуспешно (fails); Visual Prolog теперь пытается доказать эту цель путем просмотра быза правил ниже. Поскольку есть еще одна клауза, определяющая get_months_birthdays, Visual Prolog пытается доказать обращение к get_months_birthdays путем доказательства подцелей этой другой части клаузы.

Декларирование Составных Доменов

В этом разделе мы покажем как определяются составные домены. После компиляции программы, содержащей следующие отношения:

owns("John", book("From Here to Eternity", "James Jones")).

и

owns("John", horse("Blacky")).

можно запрашивать систему с помощью следующей цели:

owns("John", X)

Переменная X может быть связана с различными типами данных - книгой, лошадью или возможно другим типом, который будет определен. Поскольку предикат owns теперь определён, больше его нельзя использовать в смысле старого определения:

owns :(string, string).

Второй аргумент больше не относится к домену string. Вместо этого необходимо сформулировать новое определение предиката, такое как, например

owns :(name, articles).

Вы можете теперь описать домен articles в секции domains как показано здесь:

domains
  articles = 
    book(string Title, string Author);
    horse(string Name).
/* Articles - это книги или лошади */

Точка с запятой здесь читаются как or (или). В нашем случае возможны две альтернативы: либо книга может быть определена своим названием и автором, либо лошадь определяется своим именем. Домены Title, Author и Name - все являются стандартными доменами string.

Дополнительные альтернативы могут быть легко добавлены в декларацию домена. К примеру, articles мог бы включать лодку, дом, или банковский счёт. Для лодки, можно было бы использовать функтор без аргументов. С другой стороны, может возникнуть желание представлять баланс счета в качестве банковской записи. Тогда объявления домена articles расширяются до:

domains
  articles = 
    book(string Title, string Author);
    horse(string Name); 
    boat; 
    bankbook(real Balance).

Далее приведена полная программа, которая показывает как составной домен articles может быть использован в определениях предиката owns.

class my
domains
  articles =
    book(string Title, string Author) ;
    horse(string Name) ; 
    boat ; 
    bankbook(real Balance).
predicates
  owns :(string Name, articles) nondeterm(i,o) determ(i,i).
end class
 
implement my
  clauses
    owns("John", book("A friend of the family", "Irwin Shaw")).
    owns("John", horse("Blacky")).
    owns("John", boat).
    owns("John", bankbook(1000)).
end implement
 
goal
  console::init(),
  my::owns("John", Thing),
    stdio::write("Thing: ", Thing, "\n"),
  fail
  ;
  stdio::write("The end.").

Загрузим теперь эту программу в IDE и запустим её. Visual Prolog теперь ответит:

Thing: book("A friend of the family","Irwin Shaw")
Thing: horse("Blacky")
Thing: boat()
Thing: bankbook(1000)
The end.

Итак: правила объявлений

Дадим теперь общее представление способа записи объявлений составных доменов:

domain = 
  alternative1(D, D, ...);
  alternative2(D, D, ...);
  ...

Здесь alternative1 и alternative2 являются равновозможными (но различными) функторами. Обозначение (D, D, ...) представляет список имен, которые объявлены в любом месте либо относятся к стандартным типам доменов(таким как string, integer, real и т.д.).

Примечания:

  • Алтернативы разделяются точкой с запятой.
  • Каждая альтернатива состоит из фуктора и, возможно, списка доменов для соответствующих аргументов.
  • Если функтор не имеет аргументов, его можно записывать как alternativeN или alternativeN( ).

Многоуровневые Составные Домены

Visual Prolog позволяет организовывать многоуровневые составные домены. Например, в

book("The Ugly Duckling", "Andersen").

вместо использования фамилии автора, можно было бы использовать новую структуру, детализирующую автора глубже, включая имя и фамилию автора. Дав название новому составному домену author, теперь можно поменять описание книги на

book("The Ugly Duckling", author("Hans Christian", "Andersen")).

В старом объявлении домена

book(string Title, string Author).

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

author = author(string First_name, string Last_name).

что приводит к следующим объявлениям:

domains
    articles = book(string Title, author Author); ...
        /* First level */
    author = author(string First_name, string Last_name).
        /* Second level */

Используя составные домены таким образом на разных уровнях, часто бывает полезно рисовать дерево:

       book
       /  \
      /    \
  title   author
           /   \
          /     \
   First_name  Last_name

Одна декларация домена всегда описывает только один уровень дерева, а не все дерево. В частности, домен book не может быть объявлен такой декларацией:

/* Not allowed */
book = book(string Title, author(string First_name, string Last_name)).

Объявления Составных Смешанных Доменов

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

  • принимают аргументы более чем одного типа
  • принимают переменное число аргументов одного типа
  • принимают переменное число аргументов, каждый из которых может иметь более одного типа

Аргументы множественных типов

Для того, чтобы предикат в Visual Prolog допускал использование аргументов различного типа, необходимо использовать функторные объявления. Следующий пример, клауза your_age clause будет воспринимать аргументы типа age, которые могут иметь тип string, real или integer.

domains
    age = i(integer); r(real); s(string).
 
class predicates
    your_age :(age).
clauses
    your_age(i(Age)) :- stdio::write(Age).
    your_age(r(Age)) :- stdio::write(Age).
    your_age(s(Age)) :- stdio::write(Age).

При этом Visual Prolog не допускает декларации вида:

/* Не допустимо. */
domains
    age = integer; real; string.

Списки

Предположим, мы обрабатываем данные о предметах, которые может преподавать учитель. Код может выглядеть так:

class predicates
    teacher :(string First_name, string Last_name, string Class) determ.
 
clauses
    teacher("Ed", "Willis", "english1").
    teacher("Ed", "Willis", "math1").
    teacher("Ed", "Willis", "history1").
    teacher("Mary", "Maker", "history2").
    teacher("Mary", "Maker", "math2").
    teacher("Chris", "Grahm", "geometry").

Здесь необходимо повторять имя преподавателя для каждого предмета, который он (она) может преподавать. Для каждого предмета необходимо добавлять такой факт в базу данных. Хотя в данной ситуации это не вызывает трудности, можно найти школу, в которой сотни предметов, и тогда поддержка такой структур данных становится утомительной. Тогда было бы удобно создать такой аргумент для предиката, который мог бы воспринимать один или несколько значений.

В Прологе это делает список. В следующем коде аргумент class объявлен имеющим тип список. Покажем, как список представляется в Прологе.

domains
    classes = string*.   /* объявляет списковый домен*/
 
class predicates
    teacher :(string First_name,  string Last_name,  classes Classes) determ.
clauses
    teacher("Ed", "Willis", ["english1", "math1", "history1"]).
    teacher("Mary", "Maker", ["history2", "math2"]).
    teacher("Chris", "Grahm", ["geometry"]).

В этом примере код более лаконичен и проще читается, чем код предыдущего примера. Обратите внимание на объявление домена. Звездочка (*) означает, домен classes является списком строк. Так же легко можно объявить список целых:

domains
    integer_list = integer*.

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

domains
    integer_list = integer*.
 
class predicates
    test_scores :
        (string First_name,
          string Last_name,
          integer_list Test_Scores)
          determ.
clauses
    test_scores("Lisa", "Lavender", [86, 91, 75]).
    test_scores("Libby", "Dazzner", [79, 75]).
    test_scores("Jeff", "Zheutlin", []).

Обратите внимание, в случае Jeff Zheutlin список не содержит никаких элементов.

Еще один пример показывает как можно использовать списки для представления семейного дерева.

domains
    tree_list = tree*.
    tree = tree(string Text, tree_list TreeList).
 
class predicates
    family :(tree) determ.
clauses
    family(tree("Grandmother",
            [tree("John",
                [tree("Eric", []),
                 tree("Mark", []),
                 tree("Leonard", []) ] ),
             tree("Ellen", [])
            ]
        )).

Заключение

В этом руководстве были рассмотрены важные понятия:

  • Программа на Visual Prolog может содержать множество типов значений: простых и составных, встроенных (стандартных) и определяемых в программе.
  • Простые значения - это: числа, строки и т.д..
  • Составные домены позволяют рассматривать структуру информации как единую сущность. Составной домен состоит из имени (известного как функтор) и, возможно, аргументов. Можно определить домен с несколькими альтернативными функторами.
  • Функтор в Visual Prolog не есть то же, что функция в других языках программирования. Функтор не вызывает никаких вычислений. Это просто имя которое обозначает составной домен и объединяет его аргументы.
  • Составные значения могут обрабатываться и рассматриваться как одиночные переменные; функторы используются для создания различия между различными типами составных значений. Visual Prolog позволяет строить многоуровневые структурированные значения; Аргументами составных доменов могут быть опять-таки составные значения. Используя декларации составных смешанных доменов, можно использовать предикаты, которые:
    • принимают аргументы нескольких различных типов (определяется объявлениями функторов).
    • принимают переменное число аргументов, каждый определённого типа (объявления списков).
    • принимают переменное число аргументов, часть из которых могут иметь более одного типа.

Ссылки