Swi prolog методичка

43

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

    1. Главное меню

В папке
с установленным SWI/PROLOGвойдите в директориюpl/bin,
содержащую файлplwin.exe,
и запустите его. На экране появится
главное меню и главное (диалоговое) окно
с приглашениемSWI/PROLOG (см
рис.1).

Рис.1Вид диалогового
окнаSWI/PROLOG

Главное
меню можно сделать активным, нажав F10
илиAlt. Когда
главное меню активно, его элементы можно
выбрать с помощью клавиш управления
курсором () и последующим нажатием клавишиEnter.
Выбирать элементы главного меню можно
также и мышью.

    1. Первая программа

Программа

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

Предикат

описывают отношение между объектами,
которые являются аргументами предиката.

Факты

Констатируют наличие заданного
предикатом отношения между указанными
объектами.

ПРИМЕР

Констатация
факта в предложениии

Эллен любит
теннис.

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

Имя
предиката(функтора)
и объекта должноначинаться с маленькойбуквы и может содержать латинские буквы,
кириллицу, цифры и символ подчеркивания
(_). Кириллица используется наравне с
латинскими буквами. Обычно предикатам
дают такие имена, чтобы они отражали
смысл отношения. Например:main,
add_file_name
. Два предиката могут иметь
одинаковые имена, тогда система распознает
их как разные предикаты, если они имеют
различное число аргументов (арность).
Например, любит/2, любит/3.

Имя
предиката может совпадать с именем
какого-либо встроенного предиката
SWI/PROLOG-а. Однако, если
совпали имена пользовательского и
встроенного предиката, то при обращении
к нему (либо из интерпретатора, либо из
программы), будет вызван пользовательский
предикат, т.е. пользовательское определение
«перекроет» предопределенное вSWI/PROLOG-е.

Правила

описывают связи между предикатами.

ПРИМЕР

Билл любит все,
что любит Том.

в синтаксисе Пролога

любит(‘Билл’,Нечто):-
любит(‘Том’,Нечто).

Правило B:-Aсоответствует импликацииABЕСЛИ A , ТО B»).

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

P0:-P1,P2,…,Pn.

которая
читается «P0истинно,
еслиP1иP2и …Pnистинны».

Предикат
P0называетсязаголовком правила, выражениеP1,P2,…,Pnтелом правила, а предикатыPiподцелями правила.Запятаяозначает логическое «И«.

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

Процедура

— это совокупность утверждений,
заголовки которых имеют одинаковый
функтор и одну и ту же арность. Процедура
задает определение предиката.

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

Переменная

— поименованная область памяти, где
может храниться значение.

Если
переменная не связана со значением –
она называется свободной переменной.

Унификация

— процесс получения свободной переменной
значения в результате сопоставления
при логическом выводе в SWI/PROLOG-е.

Понятие
переменной в логическом программировании
отличается от базового понятия переменной,
которое вводится в структурном
программировании. Прежде всего, это
отличие заключается в том, что переменная
в SWI/PROLOG-е,
однажды получив свое значение при
унификации в процессе работы программы,
не может его изменить, т.е. она скорее
является аналогом математического
понятия «переменная» – неизвестная
величина. Переменная вSWI/PROLOGне имеет предопределенного типа данных
и может быть связана с значением любого
типа данных.

Переменная
в SWI/PROLOGобозначается как последовательность
латинских букв, кириллицы и цифр,
начинающаяся с заглавной буквы или
символа подчеркивания ( _ ). Заметим, что
если значение аргумента предиката или
его имя начинается с заглавной буквы,
то оно пишется в апострофах (см. предыдущий
пример).

В
SWI/PROLOGразличаются строчные и заглавные буквы.

Рассмотрим
следующую программу на SWI/PROLOG,
которую будем использовать для иллюстрации
процессов создания, выполнения и
редактирования Пролог-программ.

ПРОГРАММА 1. /* кто что любит */

любит(‘Эллен’,теннис).
%Эллен любит теннис

любит(‘Джон’,футбол).
%Джон любит футбол

любит(‘Том’,бейсбол).
%Том любит бейсбол

любит(‘Эрик’,плавание).
%Эрик любит плавание

любит(‘Марк’,теннис).
%Марк любит теннис

любит(‘Билл’,X):-любит(‘Том’,X).
%Билл любит то,
что любит Том

Комментарий
в строке программы начинается с символа
% и заканчивается концом строки. Блок
комментариев выделяется специальными
скобками: /* (начало) и */ (конец).

Для
того чтобы набрать текст программы
воспользуйтесь встроенным текстовым
редактором. Чтобы создать новый файл
выберете команду File/New,
в диалоговом окне укажите имя нового
файла, например,тест. Для редактирования
уже созданного файла с использованием
встроенного редактора можно воспользоваться
командой меню File/Edit.
Набейте программу 1 (текст программы
выше по тексту) и сохраните ее (File/Save
buffer).

Внешний
вид редактора

Красным
цветом подсвечиваются предикаты в
заголовках предложений, которые с точки
зрения синтаксиса SWI/PROLOGа
корректны. Указатель “курсор” можно
использовать для выверки (например,
корректности) расстановки скобок.
Зелёным цветом выделяются комментарии
,темно-красным цветом — переменные.
Подчеркиванием выделяются предикаты
в теле правила, которые совпадают с
предикатом заголовка,- таким образом
акцентируется внимание на возможном
зацикливании программы.

Чтобы
запустить программу, сначала необходимо
ее загрузить в SWI/PROLOGдля выполнения. Это делается выбором
опцииCompile/Compile
bufferиз окна
редактора. Результат компиляции
отображается в окне интерпретатораSWI/PROLOGа. Там
же указываются ошибки, возникшие при
компиляции, чаще всего они отображаются
и во всплывающем окне ошибок. Обычно
перед компиляцией предлагается сохранить
файл.

Другой
способ загрузить уже существующий файл
– это выполнение команды Consultв подменюFileдиалогового окнаSWI/PROLOG. На
экране появится диалоговое окно.

Укажите
имя файла, который вы хотите загрузить,
и выберите Открыть. Если вы
попытаетесь загрузить для выполнения
файл, в котором есть синтаксические
ошибки, то он не загрузится, а вы получите
сообщение об ошибке в главном окне.
Угловые скобки<< >>будут
выделять место, где встретилась ошибка.
По умолчанию файлы, ассоциируемые сSWI/PROLOGимеют
расширение.pl.

Файлы
также можно загрузить, используя
встроенный предикат:

consult(Имя
файла или имена нескольких файлов
).

ПРИМЕРЫ

consult(Test). % test – имя
файла

consult([Test1,Test2]). % Загрузка
двух файлов.

consult(‘test.pl’).1

Для
выполнения загрузки этот предикат нужно
написать в главном окне после приглашения
интерпретатора (?-), которое означает,
что интерпретатор ждет запрос.

Запрос

— это конструкция вида:

?-
P1,P2,…,Pn.

которая читается «Верно ли P1иP2и …Pn?». ПредикатыPiназываютсяподцелями запроса.

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

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

Проверьте
загрузку исходного файла, задайте запрос

?-listing.

Введите
запрос:

?-любит(‘Билл’,бейсбол).
% Любит ли Билл бейсбол?

Получите
ответ yes (да) и новое приглашение
к запросу.

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

?-любит(‘Билл’,теннис).%Любит
ли Билл теннис?

?-любит(Кто,
теннис). %Кто любит теннис?

?-любит(‘Марк’,Что),любит(‘Эллен’,Что).%Что
любят Марк и Эллен?

?-любит(Кто,
Что). %Кто что любит?

?-любит(Кто,
_). %Кто любит?

При
поиске решений в базе Пролога выдается
первое решение.

ПРИМЕР

?-любит(Кто,теннис).

Кто = ‘Эллен’

Если
необходимо продолжить поиск в базе по
этому же запросу и получить альтернативные
решения, то вводится точка с запятой ;.

Если
необходимо прервать выполнение запроса,
(например, нужно набрать другой запрос),
используйте клавишу b.

Если
Вы хотите повторить один из предыдущих
запросов, воспользуйтесь клавишами
«стрелка вверх» или «стрелка
вниз».

Перезагрузить,
измененные во внешнем редакторе, файлы
можно, используя встроенный предикат
make.
Например так:

?-make.

Перезагружаются
все измененные файлы и файл начальной
инициализации pl.ini;
о его назначении будет оговорено позднее.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Если у вас возникают проблемы с пониманием того, как вообще выполняются программ на Prolog — посмотрите статью: Введение в логическое программирование.

В этой статье описан процесс установки SWI Prolog и запуска простой программы в консоли (терминале) с помощью утилицы swipl. Однако, для SWI Prolog есть несколько сред разработки.

Установка SWI Prolog

Я буду устанавливать SWI Prolog на 64х разрядную версию OpenSUSE 42.1. Для установки открываем центр управления Yast:
swi-prolog-installation

Выбираем «Управление программным обеспечением». В появившемся окне вводим swipl, в правой части окна появляется соответствующий пакет, нажимаем на него правой кнопкой мыши и выбираем «установить»:
swi-prolog-installation_1

Теперь для работы в среде SWI Prolog можно открыть терминал и ввести команду swipl. Будет запущен интерпретатор, в который можно передавать команды:
swi-prolog-shell

Программирование в SWI Prolog

Для присоединения файлов с исходным кодом можно выполнить команду consult(путь к файлу) или [путь к файлу]. Немного удобнее использовать редактор кода (например kate или gedit) со встроенным окном терминала (для этого во многих редакторах есть специальные плагины):
swi-prolog-kate

Для работы нужно открыть в редакторе файл исходного кода, в терминале перейти в соответствующий каталог и выполнить в нем команду swipl, присоединить файл исходного кода при помощи consult. Теперь можно передавать интерпретатору команды. В приведенном выше примере, в файле описаны студенты в соответствии с заданием, мы выполняем команду student(Who, Rate) для получения информации обо всех студентах в базе.

В отличии от Visual Prolog, SWI Prolog не выводит сразу все найденные решения. Выводится первое решение, при этом если полученных данных пользователю достаточно, он может ввести точку, а если ему нужны другие решения — то нужно ввести точку с запятой.

Введем в текстовом редакторе программу в соответствии с заданием:

student('Ivanov', 4.5).
student('Fedorov', 3).
student('Vetrov', 4.3).
student('Petrov', 3.2).
student('Popov', 4.6).
student('Sidorov', 5).

stipendiya(Name):-
  student(Name, SrBal), 
  SrBal > 4.
  
company('Microsoft', 5).
company('Apple', 4.9).
company('IBM', 4.5).
company('Samsung', 4).
company('Maxim & Co', 3).

isAbleToWork(NameStudent, NameCompany):-
  student(NameStudent, SrBalStudent),
  company(NameCompany, SrBalCompany),
  SrBalStudent >= SrBalCompany.

swi-prolog-kate_1

Для обновления данных в интерпретаторе нужно повторно выполнить команду consult.

Разберем предложенную программу:

  1. предикаты company и student являются фактами (подобно предикату parent из предыдущей работы);
  2. предикат stipendya принимает имя студента, выполняет поиск студента в базе и получение его среднего балла. Затем выполняется сравнение среднего балла с константой:
    1. если сравнение проходит успешно, то предикат также завершится успешно и «снаружи» мы получим имя студента, для которого есть стипендия;
    2. если при сравнении выясняется, что балл студента меньше константы, то предикат завершается неудачей. Никакого результата (информации о студенте, с которым это случилось) «снаружи» мы при этом не получим, но будет запущен механизм поиска с возвратами (интерпретатор попробует подобрать других студентов, соответствующих нашим критериям);
    3. предикат может завершиться неудачей до выполнения сравнения — если передано имя студента, о котором нет информации в базе (мы просто не сможем получить для него средний балл);
    4. наконец, если в предикат передана анонимная переменная stipendya(Name), то будут выведены все студенты, для которых начисляется стипендия.
  3. Предикат isAbleToWork принимает на вход имя студента и имя компании, выполняет поиск среднего балла студента и минимального проходного балла компании, а затем сравнивает эти баллы. Работает аналогично предикату stipendya с той разницей, что проходной балл компании не является константой, а извлекается из базы. Если в качестве имени компании передать анонимную переменную, интерпретатор попробует подобрать все компании к нашему студенту.

Чтобы составить запрос относительно получения стипендии студентом Поповым достаточно передать имя студента в качестве аргумента правилу stipendya. При выполнении такого запроса мной были получены ошибки:
swi-prolog-debug.

Интерпретатор сообщает, что нет такого предиката в нашей программе. На самом деле, в программе содержится опечатка (имя функции написано неверно). После исправления ошибки и перекомпиляции программы командой [имя файла] или consult(имя файла) ошибка исправлена, получен ответ true, означающий, что студенту Попову положено получать стипендию.

Аналогично выполняется проверка получения стипендии студентом Федоровым (которому стипендия не положена, поэтому выводится false):
swi-prolog-goal_1

Чтобы получить имена всех студентов, получающих стипендию передадим в качестве аргумента функции stipendiya анонимную переменную:
swi-prolog-goal_2

Чтобы получить минимальный средний балл, необходимый для трудоустройства в Microsoft нужно выполнить запрос company('Microsoft', X). Во время его выполнения нужная информация будет получена непосредственно из факта company('Microsoft', 5). Результат:
swi-prolog-goal_3

Чтобы проверить может ли Попов трудоустроиться в Microsoft нужно использовать предикат isAbleToWork:
swi-prolog-goal_4

Чтобы узнать организации, в которые может устроиться на работу Федоров, нужно передать имя студента в isAbleToWork, но в качестве названия компании передать анонимную переменную:
swi-prolog-goal_5

Чтобы получить имена студентов, способных устроиться в Apple, нужно наоборот передать в isAbleToWork имя компании, а вместо имени студента указать переменную без присвоенного заранее значения:
swi-prolog-goal_6

Аналогичный запрос, но с именем компании IBM нужно выполнить для поиска студентов, которые могут трудоустроиться в эту компанию. Результаты отличаются тем, что устроиться может несколько студентов. Для получения всех студентов нужно вводить точку с запятой после каждого результата:
swi-prolog-goal_7

Выводы: во время выполнения работы были получены навыки работы в среде SWI-Prolog Исследован код программы, выданный преподавателем; во время выполнения заданий получены некоторые навыки отладки программ в интерпретаторе SWI-Prolog.

Время на прочтение
14 мин

Количество просмотров 41K

Prolog - самый популярный язык логического программирования
Prolog — самый популярный язык логического программирования

В этой статье:

  1. Узнаем, что такое логическое программирование (ЛП), и его области применения

  2. Установим самый популярный язык ЛП — Prolog

  3. Научимся писать простые программы на Prolog

  4. Научимся спискам в Prolog

  5. Разберем преимущества и недостатки Prolog.

Эта статья будет полезна для тех, кто:

  1. Интересуется необычными подходами и расширяет свой кругозор

  2. Начинает изучать Prolog (например, в институте)

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

В конце статьи я оставлю полезные ссылки. Если у вас останутся вопросы — пишите в комментариях!

Начнем туториал: Пролог для чайников!

Логическое программирование

Существуют разные подходы к программированию. Часто выделяют такие парадигмы программирования:

  • Императивное (оно же алгоритмическое или процедурное). Самая известная парадигма. Программист четко прописывает последовательность команд, которые должен выполнить процессор. Примеры: C/C++, Java, C#, Python, Golang, машина Тюрьнга, алгоритмы Маркова. Все четко, последовательно как надо. Синоним императивного — приказательное.

  • Аппликативное (Функциональное). Менее известная, но тоже широко используемая. Примеры языков: Haskell, F#, Лисп. Основывается на математической абстракции лямбда вычислениях. Благодаря чистым функциям очень удобно параллелить такие программы. Чистые функции — функции без побочных эффектов. Если такой функции передавать одни и те же аргументы, то результат всегда будет один и тот же. Такие языки обладают высокой надежностью кода.

  • И наконец — Декларативное (Логическое). Основывается на автоматическом доказательстве теорем на основе фактов и правил. Примеры языков: Prolog и его диалекты, Mercury. В таких языках мы описываем пространство состояний, в которых сам язык ищет решение к задаче. Мы просто даем ему правила, факты, а потом говорим, что «сочини все возможные стихи из этих слов», «реши логическую задачу», «найди всех братьев, сестер, золовок, свояков в генеалогическом древе», или «раскрась граф наименьшим кол-вом цветов так, что смежные ребра были разного цвета».

    Что такое ЛП я обозначил. Предлагаю сразу перейти к практике, к основам Prolog (PROgramming in LOGic). На практике все становится понятнее. Практику и теорию я буду чередовать. Не беспокойтесь, если сразу будет что-то не понятно. Повторяйте за мной, и вы разберетесь.

Установка Prolog

Существую разные реализации (имплементации) Пролога: SWI Prolog, Visual Prolog, GNU Prolog. Мы установим SWI Prolog.

Установка на Arch Linux:

sudo pacman -S swi-prolog

Установка на Ubuntu:

sudo apt install swi-prolog

Prolog работает в режиме интерпретатора. Теперь можем запустить SWI Prolog. Запускать не через swi-prolog, а через swipl:

[user@Raft ~]$ swipl
Welcome to SWI-Prolog (threaded, 64 bits, version 8.2.3)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- 

Ура! Оно работает!

Теперь поставим на Windows.
Перейдем на официальный сайт на страницу скачивания стабильной версии. Ссылка на скачивание. Клик. Скачаем 64х битную версию. Установка стандартная. Чтобы ничего не сломать, я решил галочки не снимать. Ради приличия я оставлю скрины установки.

Сайт SWI Prolog - установщики последней стабильной версии

Сайт SWI Prolog — установщики последней стабильной версии
Галочки не трогаем
Галочки не трогаем
Ищу SWI-Prolog через поиск Windows. Запускаю
Ищу SWI-Prolog через поиск Windows. Запускаю
Ура! Теперь и на Windows все работает
Ура! Теперь и на Windows все работает

Основы Prolog. Факты, правила, предикаты

Есть утверждения, предикаты:

  • Марк изучает книгу (учебник, документацию)

  • Маша видит клавиатуру (мышку, книгу, тетрадь, Марка)

  • Миша изучает математику (ЛП, документацию, учебник)

  • Саша старше Лёши

С английского «predicate» означает «логическое утверждение».

Есть объекты: книга, клавиатура, мышка, учебник, документация, тетрадь, математика, ЛП, Марк, Маша, Саша, Даша, Лёша, Миша, да что угодно может быть объектом.
Есть отношения между объектами, т.е то, что связывает объекты. Связь объектов можно выразить через глаголы, например: читать, видеть, изучать. Связь можно выразить через прилагательное. Миша старше Даши. Даша старше Лёши. Получается.. связью может быть любая часть речь? Получается так.

Прекрасно! Давайте попробуем запрограммировать эти утверждения на Прологе. Для этого нам нужно:

  1. Создать новый текстовый файл, который я назову simple.pl (.pl — расширение Пролога)

  2. В нем написать простой однострочный код на Прологе

  3. Запустить код с помощью SWI Prolog

  4. Спросить у Пролога этот факт

Файл simple.pl:

study(mark, book).

Запустим. На линуксе это делается таким образом:

[user@Raft referat]$ swipl simple.pl 
Welcome to SWI-Prolog (threaded, 64 bits, version 8.2.3)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- 

На Windows я использую notepad++ для написания кода на Прологе. Я запущу SWI-Prolog и открою файл через consult.

Видим, что он скомпилировал условия. Даже написал "1 clauses", т.е один факт

Видим, что он скомпилировал условия. Даже написал «1 clauses», т.е один факт

Что мы сделали? Мы загрузили базу знаний (те, которые мы описали в простом однострочном файле simple.pl) и теперь можем задавать вопросы Прологу. То есть система такая: пишем знания в файле, загружаем эти знания в SWI Prolog и задаем вопросы интерпретатору. Так мы будем решать поставленную задачу. (Даже видно, в начале интерпретатор пишет «?- «. Это означает, что он ждет нашего вопроса, как великий мистик)

Давайте спросим «Марк изучает книгу?» На Прологе это выглядит так:

?- study(mark, book).
true.

?- 

По сути мы спросили «есть ли факт study(mark, study) в твоей базе?», на что нам Пролог лаконично ответил «true.» и продолжает ждать следующего вопроса. А давайте спросим, «изучает ли Марк документацию?»

?- study(mark, book).
true.

?- study(mark, docs).
false.

?- 

Интерпретатор сказал «false.». Это означает, что он не нашел этот факт в своей базе фактов.

Расширим базу фактов. После я определю более строгую терминологию и опишу, что происходит в этом коде.

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

% Код на прологе. Я описал 11 фактов.
% Каждый факт оканчивается точкой ".", как в русском языке, как любое утверждение.
% Комментарии начинаются с "%".
% Интерпретатор пролога игнорирует такие комментарии.
/* А это
многострочный
комментарий
*/

% Факты. 11 штук
study(mark, book). % Марк изучает книгу
study(mark, studentbook). % Марк изучает учебник
study(mark, docs). % Марк изучает доки

see(masha, mouse). % Маша видит мышь
see(masha, book). % Маша видит книгу
see(masha, notebook). % Маша видит тетрадь
see(masha, mark). % Маша видит Марка

study(misha, math). % Миша изучает матешу
study(misha, lp). % Миша изучает пролог
study(misha, docs). % Миша изучает доки
study(misha, studentbook). % Миша изучает учебник

Терминология. Объекты данных в Прологе называются термами (предполагаю, от слова «термин»). Термы бывают следующих видов:

  1. Константами. Делятся на числа и атомы. Начинаются с маленькой буквы. Числа: 1,36, 0, -1, 123.4, 0.23E-5. Атомы — это просто символы и строки: a, abc, neOdinSimvol, sTROKa. Если атом состоит из пробела, запятых и тд, то нужно их обрамлять в одинарные кавычки. Пример атома: ‘строка с пробелами, запятыми. Eto kirilicca’.

  2. Переменными. Начинаются с заглавной буквы: X, Y, Z, Peremennaya, Var.

  3. Структурами (сложные термы). Например, study(misha, lp).

  4. Списками. Пример: [X1], [Head|Tail]. Мы разберем их позже в этой статье.

    Есть хорошая статья, которая подробно рассказывает про синтаксис и терминологию Пролога. Рекомендую её, чтобы лучше понять понятия Пролог.

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

study(mark, book). — такие конструкции называются фактами. Они всегда истинны. Если факта в базе знаний нету, то такой факт ложный. Факты нужно оканчивать точкой, так же как утверждения в русском языке.

«Задавать вопросы Прологу» означает попросить Пролог доказать наше утверждение. Пример: ?- study(mark, book). Если наше утверждение всегда истинно, то Пролог напечатает true, если всегда ложно, то false. Если наше утверждение верно при некоторых значениях переменных, то Пролог выведет значения переменных, при которых наше утверждение верно.

Давайте загрузим факты в Пролог и будем задавать вопросы. Давайте узнаем, что изучал mark. Для этого нам нужно написать «study(mark, X).» Если мы прожмем «Enter«, то Пролог нам выдаст первое попавшееся решение

?- study(mark, X).
X = book .

Чтобы получить все возможные решения, нужно прожимать точку с запятой «;«.

?- study(mark, X).
X = book ;
X = studentbook ;
X = docs.

Можем узнать, кто изучал документацию.

?- study(Who, docs).
Who = mark ;
Who = misha.

Можно узнать, кто и что изучал!

?- study(Who, Object).
Who = mark,
Object = book ;
Who = mark,
Object = studentbook ;
Who = mark,
Object = docs ;
Who = misha,
Object = math ;
Who = misha,
Object = lp ;
Who = misha,
Object = docs ;
Who = misha,
Object = studentbook.

Пролог проходится по всей базе фактов и находит все такие переменные Who и Object, что предикат study(Who, Object) будет истинным. Пролог перебирает факты и заменяет переменные на конкретные значения. Пролог выведет такие значения переменных, при которых утверждения будут истинными. У нас задача состояла только из фактов, и решение получилось очевидным.
Переменная Who перебирается среди имен mark, misha, а переменная Object среди book, studentbook, docs, lp, math.
Who не может равняться masha, потому что masha ничего не узнала согласно нашей базе фактов. Аналогично Object не может равняться tomuChevoNetuVBaze, так как такого значения не было в базе фактов. Для study на втором месте были только book, studentbook, docs, lp, math.

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

А теперь разберем правила в Прологе. Напишем ещё одну программу old.pl.

% Это факты
older(sasha, lesha). % Саша старше Лёши
older(misha, sasha). % Миша старше Саши
older(misha, dasha). % Миша старше Даши
older(masha, misha). % Маша старше Миши

% Это правило
older(X,Y) :- older(X, Z), older(Z,Y).

% X старше Y, если X старше Z и Z старше Y
% Проще: X > Y, если X > Z и Z > Y
%
% X, Y, Z - это переменные. 
% Вместо X, Y, Z подставляются конкретные значения: misha, dasha, sasha, lesha
% Main idea: если Пролог найдет среднего Z, который между X и Y, то X старше Y.

older(X,Y) :- older(X, Z), older(Z,Y) — такие конструкции называются правилами. Чтобы из факта получить правило, нужно заменить точку «.» на двоеточие дефис «:-» и написать условие, когда правило будет истинным. Правила истинны только при определенных условиях. Например, это правило будет истинно в случае, когда факты older(X,Z) и older(Z,Y) истинны. Если переформулировать, то получается «X старше Y, если X старше Z и Z старше Y». Если математически: «X > Y, если X > Z и Z > Y».

Запятая «,» в Прологе играет роль логического «И». Пример: «0 < X, X < 5». X меньше 5 И больше 0.
Точка с запятой «;» играет роль логического «ИЛИ». «X < 0; X > 5». X меньше 0 ИЛИ больше 5.
Отрицание «not(Какой-нибудь предикат)» играет роль логического «НЕ». «not(X==5)». X НЕ равен 5.

Факты и правила образуют утверждения, предикаты. (хорошая статья про предикаты)

Сперва закомментируйте правило и поспрашивайте Пролог, кто старше кого.

?- older(masha, X).
X = misha.

Маша старше Миши. Пролог просто прошелся по фактам и нашел единственное верный факт. Но.. мы хотели узнать «Кого старше Маша?». Логично же, что если Миша старше Саши И Маша старше Миши, то Маша также старше Саши. И Пролог должен решать такие логические задачи. Поэтому нужно добавить правило older(X,Y) :- older(X, Z), older(Z,Y).
Повторим вопрос.

?- older(masha, X).
X = misha ;
X = sasha ;
X = dasha ;
X = lesha ;
ERROR: Stack limit (1.0Gb) exceeded
ERROR:   Stack sizes: local: 1.0Gb, global: 21Kb, trail: 1Kb
ERROR:   Stack depth: 12,200,525, last-call: 0%, Choice points: 6
ERROR:   Probable infinite recursion (cycle):
ERROR:     [12,200,525] user:older(lesha, _5658)
ERROR:     [12,200,524] user:older(lesha, _5678)
?- 

Программа смогла найти все решения. Но что это такое? Ошибка! Стек переполнен! Как вы думаете, с чем это может быть связано? Попробуйте подумать, почему это происходит. Хорошее упражнение — расписать на бумаге алгоритм older(masha,X) так, как будто вы — Пролог. Видите причину ошибки?

Это связано с бесконечной рекурсией. Это частая ошибка, которая возникает в программировании, в частности, на Прологе. older(X, Y) вызывает новый предикат older(X,Z), который в свою очередь вызывает следующий предикат older и так далее…
Нужно как-то остановить зацикливание. Если подумать, зачем нам проверять первый предикат «older(X, Z)» через правила? Если не нашел факт, то значит весь предикат older(X, Y) ложный (подумайте, почему).
Нужно объяснить Прологу, что факты и правила нужно проверять во второй части older(Z, Y), а в первой older(X, Y) — только факты
Нужно объяснить Прологу, что если он в первый раз не смог найти нужный факт, то ему не нужно приступать к правилу. Нам нужно как-то объяснить Прологу, где факт, а где правило.
Это задачу можно решить, добавив к предикатам ещё один аргумент, который будет показывать — это правило или факт.

% Это факты
older(sasha, lesha, fact). % Саша старше Лёши
older(misha, sasha, fact). % Миша старше Саши
older(misha, dasha, fact). % Миша старше Даши
older(masha, misha, fact). % Маша старше Миши

% Это правило
older(X,Y, rule) :- older(X, Z, fact), older(Z,Y, _).

% X старше Y, если X старше Z и Z старше Y
% Проще: X > Y, если X > Z и Z > Y
%
% X, Y, Z - это переменные.
% Пролог перебирает все возможные X, Y, Z. 
% Вместо X, Y, Z подставляются misha, dasha, sasha, lesha
% Например: Миша старше Лёши, если Миша старше Саши и Саша старше Лёши

Нижнее подчеркивание «_» — это анонимная переменная. Её используют, когда нам не важно, какое значение будет на её месте. Нам важно, чтобы первая часть правила была фактом. А вторая часть может быть любой.

Запустим код.

?- older(masha, X, _).
X = misha ;
X = sasha ;
X = dasha ;
X = lesha ;
false.

Наша программа вывела все верные ответы.

Возможно, возникает вопрос: откуда Пролог знает, что изучает Марк и что Миша старше Даши? Как он понимает такие человеческие понятия? Почему ассоциируется study(mark, math) с фразой «Марк изучает математику»? Почему не с «математика изучает Марка»?. Это наше представление. Мы договорились, что пусть первый терм будет обозначать «субъект», сам предикат «взаимосвязь», а второй терм «объект». Мы могли бы воспринимать по-другому. Это просто договеренность о том, как воспринимать предикаты. Пролог позволяет нам абстрактно описать взаимоотношения между термами.

Напишем предикат для нахождения факториала от N.

factorial(1, 1).
factorial(N, F):-
	N1 is N-1,
	factorial(N1, F1),
  F is F1*N.

% В Прологе пробелы, табуляция и новые строки работают также, как C/C++.
% Главное в конце закончить предикат точкой.

«is» означает присвоить, т.е N1 будет равняться N-1. Присвоение значений переменным Пролога называется унификацией. «is» работает только для чисел. Чтобы можно было присваивать атомы, нужно вместо «is» использовать «=».

Зададим запросы. Здесь стоит прожимать Enter, чтобы получить первое решение и не попасть в бесконечный цикл.

?- factorial(1,F).
F = 1 .

?- factorial(2,F).
F = 2 .

?- factorial(3,F).
F = 6 .

?- factorial(4,F).
F = 24 .

?- factorial(5,F).
F = 120 .

?- factorial(10,F).
F = 3628800 .

Можно улучшить, добавив дополнительное условие, что N должно быть больше или равно 0. Тогда наше решение точно не попадет в бесконечный цикл.

factorial(1, 1).
factorial(N, F):-
  N >= 0,
	N1 is N-1,
	factorial(N1, F1),
  F is F1*N.

В качестве упражнения я предлагаю вам решить такие задачи:

  1. Описать свое генеалогическое древо на предикатах female(X), male(X) и parent(X,Y).

  2. Написать предикат нахождения N числа ряда Фибоначчи.

  3. Описать дерево (граф без циклов) и найти, с какими вершинами связанная заданная вершина.

Списки в Prolog

Списки — важная структура в Прологе. Списки позволяют хранить произвольное количество данных. Связный список — структура данных, состоящая из узлов. Узел содержит данные и ссылку (указатель, связку) на один или два соседних узла. Списки языка Prolog являются односвязными, т.е. каждый узел содержит лишь одну ссылку. Приложу наглядную картинку.

Кстати, ещё одна хорошая статья про списки в Прологе.

Списки в Прологе отличаются от списков в C/C++, Python и других процедурных языков. Здесь список — это либо пустой элемент; либо один элемент, называемый головой, и присоединенный список — хвост. Список — это рекурсивная структура данных с последовательным доступом.

Списки выглядят так: [],[a], [abc, bc], [‘Слово 1’, ‘Слово 2’, 1234], [X], [Head|Tail].
Рассмотрим [Head|Tail]. Это всё список, в котором мы выделяем первый элемент, голову списка, и остальную часть, хвост списка. Чтобы отделить первые элементы от остальной части списка, используется прямая черта «|».
Можно было написать такой список [X1,X2,X3|Tail]. Тогда мы выделим первые три элемента списка и положим их в X1, X2, X3, а остальная часть списка будет в Tail.

В списках хранятся данные, и нам нужно с ними работать. Например, находить минимум, максимум, медиану, среднее, дисперсию. Может нужно найти длину списка, длину самого длинного атома, получить средний балл по N предмету среди студентов группы G. Может нужно проверить, есть ли элемент Elem в списке List. И так далее. Короче, нужно как-то работать со списками. Только предикаты могут обрабатывать списки (да и в целом в Прологе все обрабатывается предикатами).

Напишем предикат для перебора элементов списка, чтобы понять принцип работы списка.

% element(Список, Элемент)
element([Head|Tail], Element) :- Element = Head; element(Tail, Element). 

?- element([1,2,3,4,5,6, 'abc', 'prolog'], Elem).
Elem = 1 ;
Elem = 2 ;
Elem = 3 ;
Elem = 4 ;
Elem = 5 ;
Elem = 6 ;
Elem = abc ;
Elem = prolog ;
false.

element([Head|Tail],Element) будет истинным, если Element равен Head (первому элементу списка) ИЛИ если предикат element(Tail, Element) истинный. В какой-то момент эта рекурсия окончится. (Вопрос читателю: когда кончится рекурсия? Какое условие будет терминирующим?) Таким образом, предикат будет истинным, если Element будет равен каждому элементу списка [Head|Tail]. Пролог найдет все решения, и мы переберем все элементы списка.

Часто бывает нужным знать длину списка. Напишем предикат для нахождения длины списка. Протестим.

% list_length(Список, Длина списка)
list_length([], 0).
list_length([H|T], L) :- list_length(T, L1), L is L1+1.

?- list_length([123446,232,2332,23], L).
L = 4.

?- list_length([123446,232,2332,23,sdfds,sdfsf,sdfa,asd], L).
L = 8.

?- list_length([], L).
L = 0.

?- list_length([1], L).
L = 1.

?- list_length([1,9,8,7,6,5,4,3,2], L).
L = 9.

Мой Пролог предупреждает, что была не использована переменная H. Код будет работать, но лучше использовать анонимную переменную _, вместо singleton переменной.

В SWI Prolog имеется встроенный предикат length. Я реализовал аналогичный предикат list_length. Если встречается пустой список, то его длина равна нулю. Иначе отсекается голова списка, рекурсивно определяется длина нового получившегося списка и к результату прибавляется единица.

Чтобы лучше понять алгоритм, пропишите его на бумаге. Последовательно, так, как делает Пролог.

Последняя задача про списки в этой статье, это определить, принадлежит ли элемент списку. Например, 1, 2, 3 и 4 являются элементами списка [1,2,3,4]. Этот предикат мы назовем list_member.

mymember(Elem, [Elem|_]).
mymember(Elem, [_|Tail]) :- mymember(Elem, Tail).

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

Преимущества и недостатки Prolog

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

Список задач, в которых Пролог удобен:

  • Искусственный интеллект

  • Компьютерная лингвистика. Написание стихов, анализ речи

  • Поиск пути в графе. Работа с графами

  • Логические задачи

  • Нечисловое программирование

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

Задача Эйнштейна

На улице стоят пять домов. Каждый из пяти домов окрашен в свой цвет, а их жители — разных национальностей, владеют разными животными, пьют разные напитки и имеют разные профессии.

  1. Англичанин живёт в красном доме.

  2. У испанца есть собака.

  3. В зелёном доме пьют кофе.

  4. Украинец пьёт чай.

  5. Зелёный дом стоит сразу справа от белого дома.

  6. Скульптор разводит улиток.

  7. В жёлтом доме живет математик.

  8. В центральном доме пьют молоко.

  9. Норвежец живёт в первом доме.

  10. Сосед поэта держит лису.

  11. В доме по соседству с тем, в котором держат лошадь, живет математик.

  12. Музыкант пьёт апельсиновый сок.

  13. Японец программист.

  14. Норвежец живёт рядом с синим домом.

Кто пьёт воду? Кто держит зебру?

Замечание: в утверждении 6 справа означает справа относительно вас.

Научиться решать логические задачи на Пролог, можно по этой статье.
Ещё одна интересная статья. В ней автор пишет программу сочинитель стихов на Prolog.

Интересная задача, которую вы можете решить на Прологе: раскрасить граф наименьшим количеством цветов так, чтобы смежные вершины были разного цвета.

Иллюстрация к задаче

Иллюстрация к задаче

Пролог такой замечательный язык! Но почему его крайне редко используют?
Я вижу две причины:

  1. Производительность

  2. Альтернативы (например, нейросетей на Python)

Пролог решает задачи методом полного перебора. Следовательно, его сложность растет как O(n!). Конечно, можно использовать отсечения, например, с помощью «!». Но все равно сложность останется факториальной. Простые задачи не так интересны, а сложные лучше реализовать жадным алгоритмом на императивном языке.

Области, для которых предназначен Пролог, могут также успешно решаться с помощью Python, C/C++, C#, Java, нейросетей. Например, сочинение стихов, анализ речи, поиск пути в графе и так далее.

Я не могу сказать, что логическое программирование не нужно. Оно действительно развивает логическое мышление. Элементы логического программирования можно встретить на практике. И в принципе, логическое программирование — интересная парадагима, которую полезно знать, например, специалистам ИИ.

Что дальше?

Я понимаю, что статью я написал суховато и слишком «логично» (вероятно, влияние Пролога). Я надеюсь, статья вам помогла в изучении основ Логического Программирования на примере Пролога.

(Мои мысли: я часто использую повторения в статье. Это не сочинение, это туториал. Лучше не плодить ненужные синонимы и чаще использовать термины. По крайней мере, в туториалах. Так лучше запоминается. Повторение — мать учения. А как вы считаете?).

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

Статью написал Горохов Михаил, успехов в обучении и в работе!

Ссылки

  1. Ссылка на скачивание SWI Prolog

  2. Синтаксис Пролога и его терминология

  3. Предикаты в Пролог

  4. Списки в Пролог

  5. Логические задачи

  6. Сочинение стихов с помощью Пролог

  7. И конечно же ссылка на Википедию

  8. Слышали о Пролог?

  9. Примеры использования Пролог

4.17 Input
and output

SWI-Prolog provides two different packages for input and output. The
native I/O system is based on the ISO standard predicates open/3,
close/1
and friends.79Actually based on
Quintus Prolog, providing this interface before the ISO standard
existed.
Being more widely portable and equipped with a
clearer and more robust specification, new code is encouraged to use
these predicates for manipulation of I/O streams.

Section 4.17.3 describes tell/1, see/1
and friends, providing I/O in the spirit of the traditional Edinburgh
standard. These predicates are layered on top of the ISO predicates.
Both packages are fully integrated; the user may switch freely between
them.

4.17.1 Predefined
stream aliases

Each thread has five stream aliases: user_input,
user_output, user_error, current_input,
and
current_output. Newly created threads inherit these stream
aliases from their parent. The user_input, user_output
and user_error aliases of the main thread are
initially bound to the standard operating system I/O streams (stdin,
stdout and stderr, normally bound to the POSIX file
handles 0, 1 and 2). These aliases may be re-bound, for
example if standard I/O refers to a window such as in the swipl-win.exe
GUI executable for Windows. They can be re-bound by the user using
set_prolog_IO/3
and set_stream/2
by setting the alias of a stream (e.g,
set_stream(S, alias(user_output))). An example of rebinding
can be found in library library(prolog_server), providing a telnet
service. The aliases current_input and current_output
define the source and destination for predicates that do not take a
stream argument (e.g., read/1, write/1, get_code/1,
… ). Initially, these are bound to the same stream as user_input
and
user_error. They are re-bound by see/1, tell/1, set_input/1
and
set_output/1.
The current_output stream is also temporary re-bound by with_output_to/2
or format/3
using e.g.,
format(atom(A), .... Note that code which explicitly writes
to the streams user_output and user_error will
not be redirected by with_output_to/2.

Compatibility

Note that the ISO standard only defines the user_*
streams. The `current’ streams can be accessed using current_input/1
and
current_output/1.
For example, an ISO compatible implementation of
write/1
is

write(Term) :- current_output(Out), write_term(Out, Term).

while SWI-Prolog additionally allows for

write(Term) :- write(current_output, Term).

4.17.2 ISO
Input and Output Streams

The predicates described in this section provide ISO compliant I/O,
where streams are explicitly created using the predicate open/3.
The resulting stream identifier is then passed as a parameter to the
reading and writing predicates to specify the source or destination of
the data.

This schema is not vulnerable to filename and stream ambiguities as
well as changes to the working directory. On the other hand, using the
notion of current-I/O simplifies reusability of code without the need to
pass arguments around. E.g., see with_output_to/2.

SWI-Prolog streams are, compatible with the ISO standard, either
input or output streams. To accommodate portability to other systems, a
pair of streams can be packed into a stream-pair. See
stream_pair/3
for details.

SWI-Prolog stream handles are unique symbols that have no syntactical
representation. They are written as <stream>(hex-number),
which is not valid input for read/1.
They are realised using a blob of type stream (see blob/2
and section 11.4.7).

[ISO]open(+SrcDest,
+Mode, —Stream, +Options
)
True when SrcDest can be opened in Mode and Stream
is an I/O stream to/from the object. SrcDest is normally the
name of a file, represented as an atom or string. Mode is one
of
read, write, append or update.
Mode
append opens the file for writing, positioning the file
pointer at the end. Mode update opens the file for writing,
positioning the file pointer at the beginning of the file without
truncating the file. Stream is either a variable, in which
case it is bound to an integer identifying the stream, or an atom, in
which case this atom will be the stream identifier.80New
code should use the alias(Alias) option for compatibility
with the ISO standard.

SWI-Prolog also allows SrcDest to be a term pipe(Command).
In this form, Command is started as a child process and if
Mode is write, output written to Stream
is sent to the standard input of Command. Viso versa, if Mode
is
read, data written by Command to the standard
output may be read from Stream. On Unix systems, Command
is handed to popen() which hands it to the Unix shell. On Windows, Command
is executed directly. See also process_create/3
from library(process).

The following Options are recognised by open/4:

alias(Atom)
Gives the stream a name. Below is an example. Be careful with this
option as stream names are global. See also set_stream/2.

?- open(data, read, Fd, [alias(input)]).

        ...,
        read(input, Term),
        ...
bom(Bool)
Check for a BOM (Byte Order Marker) or write one. If omitted,
the default is true for mode read and
false for mode write. See also stream_property/2
and especially section
2.19.1.1 for a discussion of this feature.
buffer(Buffering)
Defines output buffering. The atom full (default) defines
full buffering, line buffering by line, and false
implies the stream is fully unbuffered. Smaller buffering is useful if
another process or the user is waiting for the output as it is being
produced. See also flush_output/[0,1].
This option is not an ISO option.
close_on_abort(Bool)
If true (default), the stream is closed on an abort (see
abort/0).
If false, the stream is not closed. If it is an output
stream, however, it will be flushed. Useful for logfiles and if the
stream is associated to a process (using the pipe/1
construct).
create(+List)
Specifies how a new file is created when opening in write,
append or update mode. Currently, List
is a list of atoms that describe the permissions of the created file.81Added
after feedback from Joachim Shimpf and Per Mildner.
Defined
values are below. Not recognised values are silently ignored, allowing
for adding platform specific extensions to this set.

read
Allow read access to the file.
write
Allow write access to the file.
execute
Allow execution access to the file.
default
Allow read and write access to the file.
all
Allow any access provided by the OS.

Note that if List is empty, the created file has no
associated access permissions. The create options map to the POSIX mode
option of open(), where read map to 0444, write
to 0222 and execute to 0111. On POSIX systems, the final
permission is defined as (mode & ~umask).

encoding(Encoding)
Define the encoding used for reading and writing text to this stream.
The default encoding for type text is derived from the
Prolog flag encoding.
For binary streams the default encoding is octet.
For details on encoding issues, see section
2.19.1.
eof_action(Action)
Defines what happens if the end of the input stream is reached. Action
eof_code makes get0/1
and friends return -1, and read/1
and friends return the atom end_of_file. Repetitive reading
keeps yielding the same result. Action error is like eof_code,
but repetitive reading will raise an error. With action reset,
Prolog will examine the file again and return more data if the file has
grown.
locale(+Locale)
Set the locale that is used by notably format/2
for output on this stream. See section
4.23.
lock(LockingMode)
Try to obtain a lock on the open file. Default is none,
which does not lock the file. The value read or shared
means other processes may read the file, but not write it. The value
write or exclusive means no other process may
read or write the file.

Locks are acquired through the POSIX function fcntl() using the
command
F_SETLKW, which makes a blocked call wait for the lock to
be released. Please note that fcntl() locks are advisory and
therefore only other applications using the same advisory locks honour
your lock. As there are many issues around locking in Unix, especially
related to NFS (network file system), please study the fcntl() manual
page before trusting your locks!

The lock option is a SWI-Prolog extension.

type(Type)
Using type text (default), Prolog will write a text file in
an operating system compatible way. Using type binary the
bytes will be read or written without any translation. See also the
option encoding.
wait(Bool)
This option can be combined with the lock option. If
false (default true), the open call returns
immediately with an exception if the file is locked. The exception has
the format
permission_error(lock, source_sink, SrcDest).

The option reposition is not supported in SWI-Prolog.
All streams connected to a file may be repositioned.

[ISO]open(+SrcDest,
+Mode, —Stream
)
Equivalent to open/4
with an empty option list.
open_null_stream(—Stream)
Open an output stream that produces no output. All counting functions
are enabled on such a stream. It can be used to discard output (like
Unix /dev/null) or exploit the counting properties. The
initial encoding of Stream is utf8, enabling
arbitrary Unicode output. The encoding can be changed to determine byte
counts of the output in a particular encoding or validate if output is
possible in a particular encoding. For example, the code below
determines the number of characters emitted when writing Term.

write_length(Term, Len) :-
        open_null_stream(Out),
        write(Out, Term),
        character_count(Out, Len0),
        close(Out),
        Len = Len0.
[ISO]close(+Stream)
Close the specified stream. If Stream is not open, an
existence error is raised. See stream_pair/3
for the implications of closing a
stream pair.

If the closed stream is the current input, output or error stream,
the stream alias is bound to the initial standard I/O streams of the
process. Calling close/1
on the initial standard I/O streams of the process is a no-op for an
input stream and flushes an output stream without closing it.82This
behaviour was defined with purely interactive usage of Prolog in mind.
Applications should not count on this behaviour. Future versions may
allow for closing the initial standard I/O streams.

[ISO]close(+Stream,
+Options
)
Provides close(Stream, [force(true)]) as the only option.
Called this way, any resource errors (such as write errors while
flushing the output buffer) are ignored.
[ISO]stream_property(?Stream,
?StreamProperty
)
True when StreamProperty is a property of Stream.
If enumeration of streams or properties is demanded because either
Stream or StreamProperty are unbound, the
implementation enumerates all candidate streams and properties while
locking the stream database. Properties are fetched without locking the
stream and may be outdated before this predicate returns due to
asynchronous activity.

alias(Atom)
If Atom is bound, test if the stream has the specified alias.
Otherwise unify Atom with the first alias of the stream.bugBacktracking
does not give other aliases.
buffer(Buffering)
SWI-Prolog extension to query the buffering mode of this stream.
Buffering is one of full, line or false.
See also open/4.
buffer_size(Integer)
SWI-Prolog extension to query the size of the I/O buffer associated to a
stream in bytes. Fails if the stream is not buffered.
bom(Bool)
If present and true, a BOM (Byte Order Mark) was
detected while opening the file for reading, or a BOM was written while
opening the stream. See section
2.19.1.1 for details.
close_on_abort(Bool)
Determine whether or not abort/0
closes the stream. By default streams are closed.
close_on_exec(Bool)
Determine whether or not the stream is closed when executing a new
process (exec() in Unix, CreateProcess() in Windows). Default is to
close streams. This maps to fcntl() F_SETFD using the flag
FD_CLOEXEC on Unix and (negated) HANDLE_FLAG_INHERIT
on Windows.
encoding(Encoding)
Query the encoding used for text. See section
2.19.1 for an overview of wide character and encoding issues in
SWI-Prolog.
end_of_stream(E)
If Stream is an input stream, unify E with one of
the atoms not, at or past. See
also
at_end_of_stream/[0,1].
eof_action(A)
Unify A with one of eof_code, reset
or
error. See open/4
for details.
file_name(Atom)
If Stream is associated to a file, unify Atom to
the name of this file.
file_no(Integer)
If the stream is associated with a POSIX file descriptor, unify
Integer with the descriptor number. SWI-Prolog extension used
primarily for integration with foreign code. See also Sfileno() from
SWI-Stream.h.
input
True if Stream has mode read.
locale(Locale)
True when Locale is the current locale associated with the
stream. See section 4.23.
mode(IOMode)
Unify IOMode to the mode given to open/4
for opening the stream. Values are: read, write, append
and the SWI-Prolog extension update.
newline(NewlineMode)
One of posix or dos. If dos, text
streams will emit rn for n and discard r
from input streams. Default depends on the operating system.
nlink(-Count)
Number of hard links to the file. This expresses the number of `names’
the file has. Not supported on all operating systems and the value might
be bogus. See the documentation of fstat() for your OS and the value
st_nlink.
output
True if Stream has mode write, append
or
update.
position(Pos)
Unify Pos with the current stream position. A stream position
is an opaque term whose fields can be extracted using
stream_position_data/3.
See also set_stream_position/2.
reposition(Bool)
Unify Bool with true if the position of the stream
can be set (see seek/4).
It is assumed the position can be set if the stream has a seek-function
and is not based on a POSIX file descriptor that is not associated to a
regular file.
representation_errors(Mode)
Determines behaviour of character output if the stream cannot represent
a character. For example, an ISO Latin-1 stream cannot represent
Cyrillic characters. The behaviour is one of error (throw
an I/O error exception), prolog (write ...
escape code) or xml (write &#...; XML
character entity). The initial mode is prolog for the user
streams and
error for all other streams. See also section
2.19.1 and set_stream/2.
timeout(-Time)
Time is the timeout currently associated with the stream. See
set_stream/2
with the same option. If no timeout is specified,
Time is unified to the atom infinite.
type(Type)
Unify Type with text or binary.
tty(Bool)
This property is reported with Bool equal to true
if the stream is associated with a terminal. See also set_stream/2.
write_errors(Atom)
Atom is one of error (default) or ignore.
The latter is intended to deal with service processes for which the
standard output handles are not connected to valid streams. In these
cases write errors may be ignored on user_error.
current_stream(?Object,
?Mode, ?Stream
)
The predicate current_stream/3
is used to access the status of a stream as well as to generate all open
streams. Object is the name of the file opened if the stream
refers to an open file, an integer file descriptor if the stream
encapsulates an operating system stream, or the atom [] if
the stream refers to some other object.
Mode is one of read or write.
is_stream(+Term)
True if Term is a stream name or valid stream handle. This
predicate realises a safe test for the existence of a stream alias or
handle.
stream_pair(?StreamPair,
?Read, ?Write
)
This predicate can be used in mode (-,+,+) to create a
stream-pair from an input stream and an output stream. Mode
(+,-,-) can be used to get access to the underlying streams. If a stream
has already been closed, the corresponding argument is left unbound. If
mode (+,-,-) is used on a single stream, either Read or
Write is unified with the stream while the other argument is
left unbound. This behaviour simplifies writing code that must operate
both on streams and stream pairs.

Stream-pairs can be used by all I/O operations on streams, where the
operation selects the appropriate member of the pair. The predicate
close/1
closes the still open streams of the pair.83As
of version 7.1.19, it is allowed to close one of the members of the
stream directly and close the pair later.
The output stream
is closed before the input stream. If closing the output stream results
in an error, the input stream is still closed. Success is only returned
if both streams were closed successfully.

[ISO]set_stream_position(+Stream,
+Pos
)
Set the current position of Stream to Pos. Pos
is a term as returned by stream_property/2
using the position(Pos) property. See also seek/4.
stream_position_data(?Field,
+Pos, -Data
)
Extracts information from the opaque stream position term as returned by stream_property/2
requesting the position(Pos) property.
Field is one of line_count, line_position,
char_count or byte_count. See also line_count/2,
line_position/2, character_count/2
and byte_count/2.84Introduced
in version 5.6.4 after extending the position term with a byte count.
Compatible with SICStus Prolog.
seek(+Stream,
+Offset, +Method, -NewLocation
)
Reposition the current point of the given Stream. Method
is one of bof, current or eof,
indicating positioning relative to the start, current point or end of
the underlying object. NewLocation is unified with the new
offset, relative to the start of the stream.

Positions are counted in `units’. A unit is 1 byte, except for text
files using 2-byte Unicode encoding (2 bytes) or wchar encoding
(sizeof(wchar_t)). The latter guarantees comfortable interaction with
wide-character text objects. Otherwise, the use of
seek/4
on non-binary files (see open/4)
is of limited use, especially when using multi-byte text encodings (e.g. UTF-8)
or multi-byte newline files (e.g. DOS/Windows). On text files,
SWI-Prolog offers reliable backup to an old position using stream_property/2
and
set_stream_position/2.
Skipping N character codes is achieved calling
get_code/2 N
times or using copy_stream_data/3,
directing the output to a null stream (see open_null_stream/1).
If the seek modifies the current location, the line number and character
position in the line are set to 0.

If the stream cannot be repositioned, a permission_error
is raised. If applying the offset would result in a file position less
than zero, a domain_error is raised. Behaviour when seeking
to positions beyond the size of the underlying object depend on the
object and possibly the operating system. The predicate seek/4
is compatible with Quintus Prolog, though the error conditions and
signalling is ISO compliant. See also stream_property/2
and set_stream_position/2.

set_stream(+Stream,
+Attribute
)
Modify an attribute of an existing stream. Attribute
specifies the stream property to set. If stream is a pair (see stream_pair/3)
both streams are modified, unless the property is only meaningful on one
of the streams or setting both is not meaningful. In particular,
eof_action only applies to the read stream,
representation_errors only applies to the write
stream and trying to set alias or line_position
on a pair results in a permission_error exception. See also
stream_property/2
and open/4.

alias(AliasName)
Set the alias of an already created stream. If AliasName is
the name of one of the standard streams, this stream is rebound. Thus, set_stream(S,
current_input)
is the same as set_input/1,
and by setting the alias of a stream to user_input, etc.,
all user terminal input is read from this stream. See also interactor/0.
buffer(Buffering)
Set the buffering mode of an already created stream. Buffering is one of full, line
or false.
buffer_size(+Size)
Set the size of the I/O buffer of the underlying stream to Size
bytes.
close_on_abort(Bool)
Determine whether or not the stream is closed by abort/0.
By default, streams are closed.
close_on_exec(Bool)
Set the close_on_exec property. See stream_property/2.
encoding(Atom)
Defines the mapping between bytes and character codes used for the
stream. See section
2.19.1 for supported encodings. The value
bom causes the stream to check whether the current
character is a Unicode BOM marker. If a BOM marker is found, the
encoding is set accordingly and the call succeeds. Otherwise the call
fails.
eof_action(Action)
Set end-of-file handling to one of eof_code, reset
or
error.
file_name(FileName)
Set the filename associated to this stream. This call can be used to set
the file for error locations if Stream corresponds to
FileName and is not obtained by opening the file directly
but, for example, through a network service.
line_position(LinePos)
Set the line position attribute of the stream. This feature is intended
to correct position management of the stream after sending a terminal
escape sequence (e.g., setting ANSI character attributes). Setting this
attribute raises a permission error if the stream does not record
positions. See line_position/2
and stream_property/2
(property position).
locale(+Locale)
Change the locale of the stream. See section
4.23.
newline(NewlineMode)
Set input or output translation for newlines. See corresponding
stream_property/2
for details. In addition to the detected modes, an input stream can be
set in mode detect. It will be set to dos if a r
character was removed.
timeout(Seconds)
This option can be used to make streams generate an exception if it
takes longer than Seconds before any new data arrives at the
stream. The value infinite (default) makes the stream block
indefinitely. Like wait_for_input/3,
this call only applies to streams that support the select() system call.
For further information about timeout handling, see wait_for_input/3.
The exception is of the form

error(timeout_error(read, Stream), _)

type(Type)
Set the type of the stream to one of text or binary.
See also open/4
and the encoding property of streams. Switching to binary
sets the encoding to octet. Switching to
text sets the encoding to the default text encoding.
record_position(Bool)
Do/do not record the line count and line position (see line_count/2
and line_position/2).
Calling set_stream(S, record_position(true)) resets the
position the start of line 1.
representation_errors(Mode)
Change the behaviour when writing characters to the stream that cannot
be represented by the encoding. See also stream_property/2
and
section 2.19.1.
tty(Bool)
Modify whether Prolog thinks there is a terminal (i.e. human
interaction) connected to this stream. On Unix systems the initial value
comes from isatty(). On Windows, the initial user streams are supposed
to be associated to a terminal. See also stream_property/2.
set_prolog_IO(+In,
+Out, +Error
)
Prepare the given streams for interactive behaviour normally associated
to the terminal. In becomes the user_input and
current_input of the calling thread. Out becomes
user_output and current_output. If Error
equals
Out an unbuffered stream is associated to the same
destination and linked to user_error. Otherwise Error
is used for
user_error. Output buffering for Out is set to
line and buffering on Error is disabled. See
also prolog/0
and set_stream/2.
The clib package provides the library library(prolog_server),
creating a TCP/IP server for creating an interactive session to Prolog.

4.17.3 Edinburgh-style
I/O

The package for implicit input and output destinations is (almost)
compatible with Edinburgh DEC-10 and C-Prolog. The reading and writing
predicates refer to, resp., the current input and output
streams. Initially these streams are connected to the terminal. The
current output stream is changed using tell/1
or append/1.
The current input stream is changed using see/1.
The stream’s current value can be obtained using telling/1
for output and seeing/1
for input.

Source and destination are either a file, user, or a
term `pipe(Command)’. The reserved stream name user
refers to the terminal.85The ISO
I/O layer uses user_input, user_output and user_error.

In the predicate descriptions below we will call the source/destination
argument `SrcDest‘. Below are some examples of
source/destination specifications.

?- see(data). % Start reading from file
`data’.
?- tell(user). % Start writing to the
terminal.
?- tell(pipe(lpr)). % Start writing to the
printer.

Another example of using the pipe/1 construct is shown
below.86As of version 5.3.15, the
pipe construct is supported in the MS-Windows version, both for swipl.exe
and swipl-win.exe. The implementation uses code from the LUA
programming language (http://www.lua.org).

Note that the pipe/1 construct is not part of Prolog’s
standard I/O repertoire.

getwd(Wd) :-
        seeing(Old), see(pipe(pwd)),
        collect_wd(String),
        seen, see(Old),
        atom_codes(Wd, String).

collect_wd([C|R]) :-
        get0(C), C == -1, !,
        collect_wd(R).
collect_wd([]).

The effect of tell/1
is not undone on backtracking, and since the stream handle is not
specified explicitly in further I/O operations when using
Edinburgh-style I/O, you may write to unintended streams more easily
than when using ISO compliant I/O. For example, the following query
writes both «a» and «b» into the file `out’ :

?- (tell(out), write(a), false ; write(b)), told.

Compatibility notes

Unlike Edinburgh Prolog systems, telling/1
and seeing/1
do not return the filename of the current input/output but rather the
stream identifier, to ensure the design pattern below works under all
circumstances:87Filenames can be
ambiguous and SWI-Prolog streams can refer to much more than just files.

        ...,
        telling(Old), tell(x),
        ...,
        told, tell(Old),
        ...,

The predicates tell/1
and see/1
first check for user, the
pipe(command) and a stream handle. Otherwise, if the
argument is an atom it is first compared to open streams associated to a
file with exactly the same name. If such a stream exists,
created using
tell/1
or see/1,
output (input) is switched to the open stream. Otherwise a file with the
specified name is opened.

The behaviour is compatible with Edinburgh Prolog. This is not
without problems. Changing directory, non-file streams, and multiple
names referring to the same file easily lead to unexpected behaviour.
New code, especially when managing multiple I/O channels, should
consider using the ISO I/O predicates defined in section
4.17.2.

see(+SrcDest)
Open SrcDest for reading and make it the current input (see
set_input/1).
If SrcDest is a stream handle, just make this stream the
current input. See the introduction of section
4.17.3 for details.
tell(+SrcDest)
Open SrcDest for writing and make it the current output (see
set_output/1).
If SrcDest is a stream handle, just make this stream the
current output. See the introduction of section
4.17.3 for details.
append(+File)
Similar to tell/1,
but positions the file pointer at the end of File rather than
truncating an existing file. The pipe construct is not accepted by this
predicate.
seeing(?SrcDest)
Same as current_input/1,
except that user is returned if the current input is the
stream user_input to improve compatibility with traditional
Edinburgh I/O. See the introduction of
section 4.17.3 for details.
telling(?SrcDest)
Same as current_output/1,
except that user is returned if the current output is the
stream user_output to improve compatibility with
traditional Edinburgh I/O. See the introduction of
section 4.17.3 for details.
seen
Close the current input stream. The new input stream becomes
user_input.
told
Close the current output stream. The new output stream becomes
user_output.

4.17.4 Switching
between Edinburgh and ISO I/O

The predicates below can be used for switching between the implicit
and the explicit stream-based I/O predicates.

[ISO]set_input(+Stream)
Set the current input stream to become Stream. Thus, open(file,
read, Stream), set_input(Stream)
is equivalent to see(file).
[ISO]set_output(+Stream)
Set the current output stream to become Stream. See also
with_output_to/2.
[ISO]current_input(-Stream)
Get the current input stream. Useful for getting access to the status
predicates associated with streams.
[ISO]current_output(-Stream)
Get the current output stream.

4.17.5 Write
onto atoms, code-lists, etc.

with_output_to(+Output,
:Goal
)
Run Goal as once/1,
while characters written to the current output are sent to Output.
The predicate is SWI-Prolog-specific, inspired by various posts to the
mailinglist. It provides a flexible replacement for predicates such as
sformat/3 , swritef/3,
term_to_atom/2, atom_number/2
converting numbers to atoms, etc. The predicate format/3
accepts the same terms as output argument.

Applications should generally avoid creating atoms by breaking and
concatenating other atoms, as the creation of large numbers of
intermediate atoms generally leads to poor performance, even more so in
multithreaded applications. This predicate supports creating difference
lists from character data efficiently. The example below defines the DCG
rule term//1 to
insert a term in the output:

term(Term, In, Tail) :-
        with_output_to(codes(In, Tail), write(Term)).

?- phrase(term(hello), X).

X = [104, 101, 108, 108, 111]
A Stream handle or alias
Temporarily switch current output to the given stream. Redirection using
with_output_to/2
guarantees the original output is restored, also if
Goal fails or raises an exception. See also call_cleanup/2.
atom(-Atom)
Create an atom from the emitted characters. Please note the remark
above.
string(-String)
Create a string object as defined in section
5.2.
codes(-Codes)
Create a list of character codes from the emitted characters, similar to
atom_codes/2.
codes(-Codes, -Tail)
Create a list of character codes as a difference list.
chars(-Chars)
Create a list of one-character atoms from the emitted characters,
similar to atom_chars/2.
chars(-Chars, -Tail)
Create a list of one-character atoms as a difference list.

4.17.6 Fast
binary term I/O

The predicates in this section provide fast binary I/O of arbitrary
Prolog terms, including cyclic terms and terms holding attributed
variables. Library library(fastrw) is a SICSTus/Ciao
compatible library that extends the core primitives described below.

The binary representation the same as used by PL_record_external().
The use of these primitives instead of using write_canonical/2
has advantages and disadvantages. Below are the main considerations:

  • Using write_canonical/2
    allows or exchange of terms with other Prolog systems. The format is
    stable and, as it is text based, it can be inspected and corrected.
  • Using the binary format improves the performance roughly 3 times.
  • The size of both representations is comparable.
  • The binary format can deal with cycles, sharing and attributes.
    Special precautions are needed to transfer such terms using write_canonical/2.
    See term_factorized/3
    and copy_term/3.
  • In the current version, reading the binary format has only
    incomplete consistency checks. This implies a user must be able to trust
    the source
    as crafted messages may compromise the reading Prolog
    system.
fast_term_serialized(?Term,
?String
)
(De-)serialize Term to/from String.
fast_write(+Output,
+Term
)
Write Term using the fast serialization format to the
Output stream. Output must be a binary
stream.
fast_read(+Input,
-Term
)
Read Term using the fast serialization format from the
Input stream. Input must be a binary
stream.bugThe predicate fast_read/2
may crash on arbitrary input.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *