Программирование на языке Пролог для искусственного интеллекта

         

Ввод и вывод


Глава 6. ВВОД И ВЫВОД

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

double_line();

Обработка файлов термов



    Обработка файлов термов

    reаd  и write
1.    reаd  и
write

Встроенный предикат read используется для чтения термов из текущего входного потока. Цель

        read( X)

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

Если read( X) вычисляется в тот момент, когда достигнут конец текущего входного файла, тогда Х конкретизируется атомом end_of_file (конец файла).



Встроенный предикат write выводит терм. Поэтому цель

        write( X)

выведет терм Х в текущий выходной файл. Х будет выведен в той же стандартной форме, в какой обычно пролог-система изображает на экране или распечатке значения переменных. Полезной особенностью Пролога является то, что процедура write "знает", как изображать любой терм, как бы сложен он не был.

Существуют дополнительные встроенные предикаты для форматирования вывода. Они вставляют пробелы в дополнительные строки в выходной поток. Цель

        tab( N)

выводит N пробелов. Предикат nl (без аргументов) приводит к переходу на новую строку.

Следующие примеры иллюстрируют использование этих процедур.

Предположим, у нас есть процедура для вычисления кубов чисел:

        куб( N, С) :-
             С is N * N * N.

Предположим далее, что мы хотим применить ее для вычисления кубов элементов некоторой последовательности чисел. Это можно сделать с помощью последовательности вопросов:

        ?-  куб( 2, X).
        Х = 8

        ?-  ку6( 5, Y).
        Y = 125

        ?-  куб( 12, Z).
        Z = 1728

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

        куб :-
                read( X),
                обработать( X).

        обработать( стоп) :-   !.

        обработать( N) :-
                С is N * N * N,
                write( С),
                куб.

Это был пример программы, декларативный смысл которой трудно сформулировать. В то же время ее процедурный смысл совершенно ясен: чтобы вычислить куб, сначала нужно считать X, а затем его обработать; если Х = стоп, то все сделано, иначе вывести Х3 и рекурсивно запустить процедуру куб для обработки остальных чисел.

С помощью этой новой процедуры таблица кубов чисел может быть получена таким образом:

        ?-  куб.

        2.
        8
        5.
        125
        12.
        1728
        стоп.
        yes

Числа  2,  5  и  12  были введены пользователем с терминала, остальные числа были выведены программой. Заметьте, что после каждого числа, введенного пользователем, должна стоять точка, которая сигнализирует о конце терма.

Может показаться, что приведённую процедуру куб можно упростить. Однако следующая попытка такого упрощения является ошибочной:

        куб :-
              read( стоп),   !.

        куб :-
              read( N),
              С is N * N * N,
              write( С),
              куб.

Причина, по которой эта процедура работает неправильно, станет очевидной, если проследить, какие действия она выполняет с входным аргументом, скажем с числом 5. Цель read( стоп) потерпит неудачу при чтении этого числа, и оно будет потеряно навсегда. Следующая цель read введет следующий терм. С другой стороны может случиться, что сигнал стоп будет считан целью read( N), что приведет к попытке перемножить нечисловую информацию.

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

        куб :-
              write( 'Следующее число, пожалуйста:'),
              read( X),
              обработать( X).

        о6работать( стоп) :-   !.

        обработать( N) :-
              С is N * N * N,
              write( 'Куб'),  write( N),  write( 'равен').
              write( С), nl,
              куб.

Разговор с новой версией мог бы быть, например, таким:

        ?-  куб.

        Следующее число, пожалуйста: 5.
        Куб 5 равен 125

        Следующее число, пожалуйста: 12.
        Куб 12 равен 1728

        Следующее число, пожалуйста: стоп.
        yes

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

В последующих разделах мы увидим некоторые типичные примеры операций, в которых участвуют чтение и запись.

    Вывод списков
2.    Вывод списков

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

        вывспис( L)

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

        вывспис( [ ]).

        вывспис( [X | L) :-
                write( X), n1.
                вывспис( L).

Если у нас есть список списков, то одной из естественных форм его выводе является такая, при которой все элементы каждого списка записываются на отдельной строке. Для этого мы определим процедуру вывспис2. Вот пример ее использования:

        ?-  вывспис2( [ [а, b, с], [d, e, f], [g, h, i] ] ).

        а   b   с
        d   e   f
        g   h   i

Процедура, выполняющая эту работу, такова:

        вывспис2( [ ]).

        вывспис2( [L | LL] ) :-
                строка( L), n1,
                вывспис1( LL).

        строка( [ ]).

        строка( [X | L] ) :-
                write( X), tab( 1),
                строка( L).

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

        ?- диагр( [3, 4, 6, 5] ).

        ***
        ****
        ******
        *****

        Процедуру диагр можно определить так:

                диагр( [N | L]) :-
                    звездочки( N), n1,
                    диагр( L).

                звеэдочки( N) :-
                    N > 0,
                    write( *),
                    Nl is N - 1,
                    звездочки( Nl).

                звездочки( N) :-
                    N =< 80.

    Формирование термов
3.    Формирование термов

Предположим, наша программа имеет дело с семьями, которые представлены в виде термов так, как это сделано в гл. 4 (рис. 4.1). Тогда, если, перемен-

line();

        родители
                том фокс, датарожд 7 май 1950, работает bbс,
                                                                        оклад 15200
                энн фокс, датарожд 9 май 1951, неработает
        дети
                пат фокс, датарожд 5 май 1973, неработает
                джим фокс, датарожд 5 май 1973, неработает

line();



Обработка символов



    Обработка символов

Символ записывается в текущий выходной поток при помощи цели

        put( С)

где С - символ, который нужно вывести, в кодировке ASCII (число от 0 до 127), например, вопрос

        ?- put( 65), put( 66), put( 67).

породит следующий вывод:

        АВС

65 - ASCII-код 'А', 66 - 'В', 67 - 'С'.

Одиночный символ можно считать из текущего входного потока при помощи цели

        get0( С)

Она вызывает чтение символа из входного потока, и переменная С конкретизируется ASCII-кодом этого символа. Вариантом предиката get0 является get, который используется для чтения символов, отличных от пробела. Поэтому цель

        get( С)

вызовет пропуск всех непечатаемых символов (в частности пробелов) от текущей позиции во входном потоке до первого печатаемого символа. Этот символ затем тоже считывается и С конкретизируется его ASCII-кодом.

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

        Робот    пытался     налить    вина    из     бутылки.

Цель сжатие выведет его в таком виде:

        Робот пытался налить вина из бутылки.

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

        сжатие :-
                get0( С),
                put( С).
                сделатьостальное( С).

        сделатьостальное( 46) :-  !.
                                    % 46 -АSСII-код точки, Все сделано

        сделатьостальное( 32) :-  !,
                                    % 32 - ASCII-код пробела

                get( С),
                put( С),
                сделатьостальное( С).

        сделатьостальное( Буква) :-
                сжатие.



Процедура для преобразования предложения в список атомов.



  Процедура для преобразования предложения в список атомов.

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



Программа, обеспечивающая вывод в формате, представленном на рис. 6.2.



  Программа, обеспечивающая вывод в формате, представленном на рис. 6.2.

    Обработка произвольного файла термов
4.    Обработка произвольного файла термов

Типичная последовательность целей для обработки файла F от начала до конца будет выглядеть примерно так:

        . . . , see( F), обработкафайла, sеe( user), . . .

Здесь обработкафайла - процедура, которая читает и обрабатывает последовательно каждый терм файла F один за другим до тех пор, пока не встретится конец файла. Приведем типичную схему для процедуры

        обработкафайла:
                обработкафайла :-
                        read( Терм),
                        обработка( Терм).

        обработка( end_of_file) :-   !.
                                    % Все сделано

        обработка( Терм) :-
                обраб( Терм),
                                    % Обработать текущий элемент
                обработкафайла.
                                    % Обработать оставшуюся часть файла

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

        показфайла( N) :-
                read( Терм),
                показ( Терм, N).

        показ( Терм, N) :-  !
                write( N), tab( 2), write( Терм),
                Nl is N + 1,
                показфайла( Nl).

Вот другой пример программы обработки файлов, построенной по подобной схеме. Пусть есть файл с именем файл1, термы которого имеют форму

        изделие( НомерИзд, Описание, Цена, ИмяПоставщика)

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

        создатьфайл( Поставщик)

Например, если исходный каталог хранится в файле файл1, а мы хотим создать специальный каталог в файле файл2, содержащий всю информацию о том, что поставляет Гаррисон, тогда мы применим процедуру создатьфайл следующим образом:

        ?-  seе( файл1), tеll( файл2), создатьфайл( гаррисон),
                  see( user), tell( user).

Процедуру создатьфайл можно определить так:

        создатьфайл( Поставщик) :-
                write( Поставщик), write( '.'), nl,
                создатьостальное( Поставщик).

        создатьостальное( Поставщик) :-
                read( Изделие),
                обработать( Изделие, Поставщик).

        обработать( end_ot_file) :-  !.

        обработать( Изделие( Ном, Опис, Цена, Поставщик),
                                Поставщик) :-  !,
                write( Изделие( Ном, Опис, Цена) ),
                write( '.'), nl,
                создатьостальное( Поставщик).

            обработать ( _, Поставщик) :-
                создатьостальное( Поставщик).

Обратите внимание на то, что обработать вписывает точки между термами, чтобы впоследствии файл мог быть прочитан процедурой read.



к программе) осуществляется посредством встроенных



Резюме

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

    sее( Файл)                 Файл становится текущим входным потоком
    tell( Файл)                  Файл становится текущим выходным потоком
    seen                             закрывается текущий входной поток
    told                              закрывается текущий выходной поток
Файлы читаются и записываются двумя способами:

    как последовательности символов
    как последовательности термов

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

    rеad( Терм)
                            вводит следующий терм
    write( Терм)
                            выводит Терм
    put( КодСимвола)
                            выводит символ с заданным ASCII - кодом
    get0( КодСимвола)
                            вводит следующий символ
    gеt( КодСимвола)
                            вводит ближайший следующий "печатаемый" символ
Две процедуры облегчают форматирование:

    nl                       начинает новую строку
    tab( N)              выводит N пробелов
Процедура nаmе( Атом, СписокКодов) осуществляет синтез и декомпозицию атомов. СписокКодов - список ASCII кодов символов, образующих Атом.


Создание и декомпозиция атомов



    Создание и декомпозиция атомов

Часто желательно информацию, считанную как последовательность символов, иметь в программе в виде атома. Для этой цели существует встроенный предикат name. Он устанавливает взаимосвязь между атомами и их кодировкой в ASCII. Таким образом,

        name( A, L)

истинно, если L - список кодов ASCII, кодирующих атом. Например,

        name( zx232, [122, 120, 50, 51, 50] )

истинно. Существуют два типичных способа использования name:

(1)        дан атом, разбить его на отдельные символы;

(2)        дан список символов, объединить их в один атом.

Примером первого случая применения предиката является программа, которая имеет дело с заказами такси и водителями. Все это представлено в программе атомами

        заказ1, заказ2, водитель1, водитель2, такси1, таксилюкс

Предикат

        такси( X)

проверяет, относится ли атом Х к тем атомам, которые представляют такси:

        такси( Х) :-
              name( X, Хспис),
              nаmе( такси, Тспис),
              конк( Тспис, _, Хспис).

        конк( [ ], L, L).

        конк( [А | L1], L2, [А | L3] ) :-
              конк( L1, L2, L3).

Предикаты заказ и водитель можно определить аналогично.

Наш следующий пример иллюстрирует применение объединения отдельных символов в один атом. Мы определим предикат

        читпредложение( Списслов)

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

        Мэри было приятно видеть неудачу робота.

то цель читпредложение( Предложение) вызовет конкретизацию

        Предложение=['Мэри', было, приятно, видеть, неудачу, робота]

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

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

(1)        Симв - точка, тогда все сделано.

(2)        Симв - пробел, - игнорировать его и читпредложение от остального ввода.

(3)        Симв - буква, - сначала считать слово Слово, которое начинается с Симв, а затем запустить читпредложение, чтобы считать оставшуюся часть предложения, породив при этом Списслов. Общим результатом этого будет список [Слово | Списслов].

Процедура, считывающая символы одного слова, такова:

        читбуквы( Буква, Буквы, Сделсимв)

Ее три аргумента:

(1)        Буква - текущая буква (уже считанная) читаемого слова.

(2)        Буквы - список букв (начинающийся с буквы Буква), оставшихся до конца слова.

(3)        Следсимв - входной символ, непосредственно следующий за читаемым словом. Следсимв не должен быть буквой.

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

line();

/*
Процедура читпредложение считывает предложение и из его слов создает список атомов. Например,
        читпредложение( Списслов)
порождает
        Списслов=['Мэри', было, приятно, видеть, неудачу, робота]
если входным было предложение
        Мэри было приятно видеть неудачу робота.

*/

        читпредложение( Списслов) :-
                gеt0( Симв),
                читостальное( Симв, Списслов).

        читостальное( 46, [ ]) :-  !.
                                    % Конец предложения: 46 = ASCII-код для ' '

        читостальное( 32, Списслов) :-  !,
                                    % 32 = ASCII-код для пробела
        читпредложение( Списслов).
                                    % Пропустить пробел

        читостальное( Буква, [Слово | Списслов]) :-
                читбуквы( Буква, Буквы, Следсимв),
                                    % Считать буквы текущего слова
                nаmе( Слово, Буквы),
                читостальное( Следсимв, Списслов).

        читбуквы( 46, [ ], 46) :-   !.
                                    % Конец слова: 46 = точка
        читбуквы( 32, [ ], 32) :-   !.
                                    % Конец слова: 32 = пробел

        читбуквы( Бкв, [Бкв | Буквы], Следсимв) :-
                get0( Симв),
                читбуквы( Симв, Буквы, Следсимв).

line();



Связь между пролог-программой и различными файлами.



 
Связь между пролог-программой и различными файлами.

предикатов, применяемый во многих реализациях. Однако за деталями и специфическими особенностями следует, конечно, обращаться к руководствам по конкретным пролог-системам.

Рассмотрим вначале вопрос о том, как обмениваться информацией с файлами, а затем - как можно вводить и выводить данные в разных форматах.

На рис. 6.1 показана общая ситуация, в которой пролог-программа взаимодействует с несколькими файлами. Она может, в принципе, считывать данные из нескольких входных файлов, называемых также входными потоками, и выводить данные в несколько выходных файлов, называемых выходными потоками. Информация, поступающая с пользовательского терминала, рассматривается просто как еще один входной поток. Аналогично информация, выводимая на этот терминал, рассматривается как один из выходных потоков. На оба этих "псевдофайла" ссылаются с помощью имени user (пользователь). Имена остальных файлов программист должен выбирать в соответствии с правилами именования файлов, принятыми в используемой компьютерной системе.

В каждый момент выполнения пролог-программы лишь два файла являются "активными": один для ввода, другой - для вывода. Эти два файла называются текущим входным потоком и текущим выходным потоком соответственно. В начальный момент эти два потока соответствуют терминалу. Текущий входной поток может быть заменен на другой файл ИмяФайла посредством цели

        see( ИмяФайла)                                     ( Смотри(ИмяФайла) )

Такая цель всегда успешна (если только с файлом ИмяФайла все в порядке), а в качестве побочного эффекта происходит переключение ввода с предыдущего входного потока на файл ИмяФайла. Поэтому типичным примером использования предиката see является следующая последовательность целей, которая считывает информацию из файла файл1, а затем переключается обратно на терминал:

        . . .
        see( файл1),
        читать_из_файла( Информация),
        see( user),                                 ( user - пользователь)
        . . .

Текущий выходной поток может быть изменен при помощи цели вида

        tell( ИмяФайла)                       ( сообщить( ИмяФайла) )

Следующая последовательность целей выводит некоторую информацию в файл3, а после этого перенаправляет последующий вывод обратно на терминал:

        . . .
        tell( файл3),
        записать_в_файл( Информация),
        tell( user),
        . . .        

        Цель

        seen                                          ( конец чтения)

закрывает текущий входной файл. Цель

        told                                           ( конец записи)

закрывает текущий выходной файл.

Файлы могут обрабатываться только последовательно. В этом смысле все файлы ведут себя так же, как терминал. Каждый запрос на чтение из входного файла приводит к чтению в текущей позиции текущего входного потока. После этого чтения текущая позиция, естественно, будет перемещена на следующий, еще не прочитанный элемент данных. Следующий запрос на чтение приведет к считыванию, начиная с этой новой текущей позиции. Если запрос на чтение делается в конце файла, то в качестве ответа на такой запрос выдается атом end_of_file (конец файла). Считанную один раз информацию считать вторично невозможно.

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

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

Существуют два основных способа, с помощью которых файлы рассматриваются в Прологе в зависимости от формы записанной в них информации. Один способ - рассматривать символ как основной элемент файла. Соответственно один запрос на ввод или вывод приведет к чтению или записи одного символа. Для этой цели предназначены встроенные предикаты get, get0 и put (получить, получить0 и выдать).

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

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



Связь с файлами



    Связь с файлами

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

Встроенные предикаты, предназначенные для этого расширения, отличаются в разных реализациях Пролога. Мы изучим здесь простой и удобный набор таких



Улучшенный формат вывода термов, представляющих семью.



  Улучшенный формат вывода термов, представляющих семью.

ная F конкретизирована термом, изображенный на рис. 4.1, то цель

        write( F)

вызовет вывод этого терма в стандартной форме примерно так:

        семья( членсемьи( том, фокс, дата( 7, май,1950),
                         работает( bbс, 15200)),
                членсемьи( энн, фокс, дата( 9, май, 1951),
                         неработает),
                [членсемьи( пат, фокс, дата( 5, май, 1973),
                         неработает),
                членсемьи( джим, фокс, дата( 5, май, 1973),
                         неработает)])

Здесь содержится полная информация, однако форма представления легко может запутать, поскольку трудно проследить, какие ее части образуют самостоятельные семантические единицы. Поэтому обычно предпочитают выводить такую информацию в каком-либо формате, например так, как показано на рис. 6.2. Процедура

        вывсемью( F)

с помощью которой это достигается, приведена на рис. 6.3.

line();

        вывсемью( семья ( Муж, Жена, Дети) :-
                nl, write( родители), nl, nl,
                вывчленсемьи( Муж), nl,
                вывчленсемьи( Жена), nl, nl,
                write( дети), nl, nl,
                вывчленсемьи( Дети).

        вывчленсемьи( членсемьи( Имя, Фамилия, дата( Д, М, Г), Работа) ) :-
                tab(4), write( Имя),
                tab(1), write( Фамилия),
                write( ', дата рождения'),
                write( Д), tab( 1),
                write( M), tab( 1),
                write( Г), write( ','),
                вывработу( Работа).

        вывсписчлсемьи( [ ]).

        вывсписчлсемьи( [Р | L]) :-
                вывчленсемьи( Р), nl,
                вывсписчлсемьи( L),
        вывработу( неработает) :-
                write( неработает).

        вывработу( работает Место, Оклад) ) :-
                write(' работает '), write( Место),
                write( ', оклад '), write( Оклад).

line();



Обобщите процедуру сжатие на случай



Упражнение

    Обобщите процедуру сжатие на случай запятых. Все пробелы, стоящие непосредственно перед запятой, нужно убрать, а после каждой запятой нужно поместить единственный пробел.


которая выводит на терминал новый



Упражнения

    Пусть f  -   файл термов. Определите процедуру
        найтитерм( Терм)
которая выводит на терминал новый терм из f, сопоставимый с Терм'ом.
Посмотреть ответ
    Пусть f  -   файл термов. Напишите процедуру
        найтивсетермы( Терм)
которая выводит на экран все термы из f, сопоставимые с Tepм'ом. Обеспечьте при этом, чтобы во время поиска Терм не конкретизировался (это могло бы помешать ему сопоставиться с другими термами дальше по файлу).
Посмотреть ответ

для проверки, начинается ли Атом



Упражнения

    Определите отношение
        начинается( Атом, Символ)
для проверки, начинается ли Атом с символа Символ.
Посмотреть ответ
    Определите процедуру plural, которая преобразует английские существительные из единственного числа во множественное, добавляя к слову окончание s. Например:
        ?-  plural( table, X).
        Х  =  tables
Посмотреть ответ
    Напишите процедуру
        поиск( Ключслово, Предложение)
которая при каждом вызове находит в текущем входном файле предложение, содержащее заданное ключевое слово Ключслово. Предложение в своей исходной форме должно быть представлено в виде последовательности символов или в виде атома (процедуру читпредложение из данного раздела можно соответственно модифицировать).

Ввод программ: consult, reconsult



    Ввод программ:    consult, reconsult

Передавать программы пролог-системе можно при помощи двух встроенных предикатов:    consult     и    reconsult.     Чтобы система считала программу из файла F, нужно поставить цель

        ?-  consult( F).

В результате все предложения программы, содержащейся в F, будут использованы пролог-системой при ответе на дальнейшие вопросы пользователя. Если позже в том же сеансе произойдет "консультация" с другим файлом, предложения этого нового файла будут просто добавлены в конец текущего множества предложений.

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

        ?-  consult( user).

После этого система будет ожидать ввода предложений программы с терминала.

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

        ?-  [файл1, файл2, файл3].

Это в точности эквивалентно следующим трем целям:

        ?-  соnsult( файл1), соnsult( файл2), соnsult( файл3).

Встроенный предикат reconsult аналогичен consult. Цель

        ?-  reconsult( F).

даст тот же эффект, что и consult( F) с одним исключением. Если в F есть предложения, касающиеся отношений, которые уже были определены ранее, старые определения заменяются на новые из F. Разница между consult и reconsult в том, что consult всегда добавляет новые предложения, в то время как reconsult переопределяет ранее введенные определения. Однако reconsult не произведет никакого эффекта на те отношения, о которых в F ничего не сказано.

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