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

В данной статье я собираюсь рассказать о приложении (написанное мной) для Андроида, интерпретирующее Пролог программы написанные синтаксисом разработанным в 70-е года (тоесть в первоначальном своём виде: факты и правила, операторы и вопросы; о нём вы можете почитать в книге “Иван Братко, Программирование на языке Пролог для искусственного интеллекта”, ссылка находится в конце статьи). Называется оно Prolog Classic (ссылка для скачивания находится в конце статьи).

Данное приложение написано на Java n-ide для Андроид.

1. Особенности языка Пролог
Итак, Пролог — язык логического программирования, основанный на языке предикатов математической логики дизъюнктов Хорна, представляющей собой подмножество логики предикатов первого порядка.

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

Prolog является декларативным языком программирования: логика программы выражается в терминах отношений, представленных в виде фактов и правил. Для того чтобы инициировать вычисления, выполняется специальный запрос к базе знаний, на которые система логического программирования генерирует ответы «истина» и «ложь». Для обобщённых запросов с переменными в качестве аргументов созданная система Пролог выводит конкретные данные в подтверждение истинности обобщённых сведений и правил вывода.
Задача пролог-программы заключается в том, чтобы доказать, является ли заданное целевое утверждение следствием из имеющихся фактов и правил.

В данной реализации Пролога написанной для Андроид, представлены почти все возможности изначального Пролога описанного в книге Братко [1]: безтиповое программирование; программное определение новых операторов; возможность задания нескольких вопросов в одной программе; извлечение знаний (фактов, правил, вопросов, операторов) из файлов; программная запись новых структур (фактов, правил, вопросов, операторов) в базу знаний, а также удаление из неё.
Подробнее о Прологе вы можете прочитать, также, в статье-учебнике [2] .
Рассмотрим несколько программ-примеров.

1.1. Работа соператорами

:- op( 1, xfx, родитель).
игорь родитель марии.
анна родитель марии.
?- Кто родитель марии.

В консоли мы получим такой ответ:
Кто = игорь
Если мы поставим точку с запятой, то Пролог выдаст нам другой ответ:
Кто = игорь;
Кто = анна
И снова поставив точку с запятой мы узнаем, что больше родителей марии нет:
Кто = игорь;
Кто = анна;
no.
Теперь, усложним задачу. Добавим ещё два оператора и по-новому зададим факты:

:- op( 1, fx, отец).
:- op( 1, fx, мать).
:- op( 2, xfx, родитель).
отец игорь родитель марии.
мать анна родитель марии.
отец вася родитель ирины.
мать ольга родитель ирины.

Задав вопрос:
?- Кто родитель марии.
мы теперь получим следующие два ответа, использовав точку с запятой:
Кто = отец игорь;
Кто = мать анна
Поставив точку мы завершим программу.
Теперь зададим такой вопрос, чтобы узнать, кто отец кого:
?- отец Кто родитель Кого.
и получим ответы (использовав точку с запятой):
Кто = игорь, Кого = марии;
Кто = вася, Кого = ирины.

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

1.2. Безтиповое программирование
К примеру, напишем программу, которая вставляет заданный символ в конец списка из других символов:

add(X,[],[X]).
add(X,[Y|L1],[Y|L2]):- add(X,L1,L2).
?-   add(z,[a,b,c,d,e,f],L).
Результатом будет:
L = [a,b,c,d,e,f,z].

Видно, что этот предикат состоит из двух частей - факта и правила. При выполнении программы вызов этого предиката приводит к перебору правил в порядке сверху-вниз, каждый раз переданный список аргументов предиката сопоставляется с аргументами факта или правила. Если сопоставление невозможно — то выполняется переход к следующему факту или правилу. Так, первый факт описывает следующее «если список, переданный во втором аргументе пуст, то есть сопоставляется с [] — то список третьего аргумента должен состоять из одного элемента X - [X]». Если список непустой -то управление получит правило, которое:

  • отделяет от исходного списка один элемент, получая список остальных элементов L1. Конструкция [Y|L1];
  • инициирует рекурсивную обработку L1;
  • получив результат рекурсивного вызова в переменной L2, дописывает к нему в начало элемент Y. Конструкция [Y|L2].

Механизм выбора правил отдаленно напоминает полиморфизм функций в языках типа С++, но является более гибким, так как выбор происходит во время выполнения программы, а не при компиляции.
Как видите это очень просто, в Прологе, и весьма компактно (программа занимает 3 строчки).

Теперь усложним задачу, добавим в список среди символов цифры и зададим программе, чтобы она каждую цифру умножала на 2, а каждый символ ‘a’ удваивала, таким образом получив строку из двух символов - ‘aa’:

listFoo([],[]).
listFoo([N|L1],[N2|L2]):-
  numeral(N), N2 = N*2,
  listFoo(L1,L2).
listFoo([a|L1],[aa|L2]):-
  listFoo(L1,L2).
listFoo([C|L1],[C|L2]):-
  listFoo(L1,L2).
?- listFoo([1,a,b,c,3,str,ok(m)],L).

Теперь заданный список состоит из чисел, символов и строк. Трансформируя его заданным нами предикатом listFoo мы получим:
L = [2,aa,b,c,6,str,ok(m)].
Заметьте, в этой программе используется встроенный предикат numeral (N), который бывает истинным, если его аргумент является числом. Так же предикат равенства имеет две функции: если одну или две стороны можно вычислить, а потом унифицировать, то мы получим вычисления, в противном случае, Пролог попытается просто унифицировать левую и правую часть равенства. Здесь мы применяем его для вычислений (В книге Братко не так, там для вычислений используется специальный оператор is. Однако я когда-то, в другой советской книжке читал, непомню автора и названия, там равенство применялось так же как у меня).

Вот мы с вами написали программу работающую со списком, у которого елементы списка могут быть произвольного типа, а точнее никакого типа нет - есть только термы (некая древовидная структура имеющая функтор и аргументы (или ноль аргументов)). Да и сам список нигде не обьявляется, ни типы аргументов нашего предиката listFoo. В строго типизированных языках типа С++ не так просто в один контейнер поместить и числа и строки, и символы одновременно, и там в функиях нужно объявлять типы входных и выходных переменных.

1.3. Задание нескольких вопросов в программе
Напишем такую программу:

мужчина(олег).
мужчина(димитрий).
женщина(алла).
женщина(маша).
?- write(мужчины:), nl,
    мужчина (М);
    write(женщины), nl,
    женщина (Ж).

Здесь, в вопросе, мы использовали точку с запятой. Она соответствует логическому ИЛИ. Это значит, что то что до точки с запятой является одним вопросом, после неё другим. Посмотрим как это работает.
Запустив программу мы увидим:
мужчины:
М = олег
Перебирая другие ответы мы получим:
мужчины:
М = олег;
М = димитрий;
женщины:
Ж = алла;
Ж = маша;
no.
Таким образом, с помощью многих вопросов мы можем получить от компьютера исчерпывающую информацию.

Далее привожу таблицу встроенных предикатов моего приложения. Тех предикатов, которых нет в Братко я выделил жирным шрифтом цветом:
! - Отсечение.
atom(A) - Является истинным, если терм А является атомом.
functor_chars(T,CL) - Является истинным, если CL - список символов из которых состоит функтор терма Т.
retractA(T) - Является истинным, если в базе знаний существет такой факт или правило Т, кторое предикат и удаляет. Поиск ведётся с начала базы знаний.
retractZ(T) - Является истинным, если в базе знаний существет такой факт или правило Т, кторое предикат и удаляет. Поиск ведётся с конца базы знаний.
assertA(T) - Всегда является истинным и добавляет Т как факт или правило в начало базы знаний.
assertZ(T) - Всегда является истинным и добавляет Т как факт или правило в конец базы знаний.
read(T) - Читает терм с текущего потока ввода (консоль или файл) и является истинным, если прочитанный терм равен терму Т.
write(T) - Всегда является истинным и выводит терм Т в текущий потока вывода (консоль или файл).
nl - Всегда является истинным и выводит поток вывода (консоль или файл) на новую строку.
tab(N) - Является истинным, если N является положительным целочисленным числом и выводит в поток вывода (консоль или файл) количество пробелов указанных числом N.
numeral(T) - Всегда является истинным, если терм Т является числом.
T1 <= T2 - Является истинным, если число Т1 меньше или равно Т2.
T1 < T2 - Является истинным, если число Т1 меньше Т2.
T1 >= T2 - Является истинным, если число Т1 больше или равно Т2.
T1 > T2 - Является истинным, если число Т1 больше Т2.
T1 = T2 - Является истинным: 1) Если одну или две стороны можно вычислить, а потом унифицировать; 2) В противном случае, если можно просто унифицировать левую и правую часть равенства.
var(V) - Является истинным, если V является свободной переменной.
see(Dir) - Является истинным, если Dir это атом в котором указан путь и имя существующего файла. Предикат устанавливает данный файл как поток ввода. Если Dir равен user потоком ввода будет консоль, при этом открытые файлы не закрываются.
seen - Является истинным, если текущий поток ввода является файлом, который и закрывает.
time(N) - Является истинным, если N равно прошедшему времени в миллисекундах начиная от 00:00 01.01.1970 года (этот предикат полезен для генерации случайных чисел).
not(P) - Логическое отрицание. Истиннен, если предикат P является ложным в любом случае.

2. Использование интерпретатора Пролог на Android
Поскольку приложение является интерпретатором — оно не создаёт apk файл. С другой стороны это может быть даже удобно, так как пользователь всегда может что-то изменить в программе по своему усмотрению, или просто посмотреть как написана программа и из текста понять её работу целиком или каких-то частей.

Итак, поговорим о том как работать в данном приложении, разберём по шагам:

  1. Запускаем приложение. Сразу после запуска появится окно для ввода текста программы;
  2. Для открытия записанной программы ипользуем кнопку Open, для сохранения — Save, для очистки окна — New. Программы нужно сохранять только с расширением *.txt, иначе не сможете их открыть;
  3. После написания программы запускаем его кнопкой Run, после чего появится консольное окно, в котором будет отображаться результат исполнения программы («yes.», «no.» или значения переменных в вопросе). Когда результатом будут значения переменных, можно либо завершить программу точкой, либо вводом точки с запятой получить значения переменных следующего результата программы. Если в программе допущена синтаксическая ошибка (обычно это нехватка запятой или точки), то при запуске программы на экран выведится соответствующее предупреждение;
  4. Приложение позволяет не только статически указывать предикаты в программе (факты, правила, вопросы), но и динамически записывать читая их из файла или консоли;
  5. Что касается отладки программ: специального отладчика в программе нет, но я в таком случая всегда пользуюсь предикатом write/1 для вывода на экран любой информации вовремя исполнения программы.


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

3. Особенности реализации и работы интерпретатора
Теперь я вам немного расскажу о важных моментах реализации моего приложения на java языке:

3.1. Использование и реализация стэка
Стэк в моём приложении Prolog Classic нужен не только для хранения значений переменных в предикатах, но и для хранения точек отката. Получается у нас двухмерная матрица.

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

3.2. Механизм работы интерпретатора
Ядром интерпретатора это выполнение предикатов при помощи вложенной рекурсии. Тоесть, берётся первый предикат тела вопроса, унифицируется с подходящим фактом или правилом, и дальше, главная функция интерпретатора вызывает саму себя, но с новыми параметрами. Если данный предикат — правило, тогда выполняется первый предикат тела данного правила, если факт — тогда следующий предикат вопроса. И далее всё по аналогии.

Программа выполняется в отдельном потоке. Это нужно для того, чтобы видеть исполнение программы в реальном времени (например, в каком-нибудь цикле постоянно что-то выводится на экран и этот цикл продолжается долго).

3.3. Преобразование предложений программы в прологовские структуры
Для работы программы, интерпретатор должен первоначально преобразовать приложения текста в структуры (факты, правила, вопросы). Для этого используется операторная нотация [1].
Рассмотрим, к примеру, следующий вопрос в программе:
?- мужчина.
‘?-’ здесь является префиксным оператором, тоесть он должен стоять в предложении перед своим аргументом. Это предложение равносильно предложению записанному так:
?-(мужчина).
Рассмотрим другой пример:
?- вася слесарь.
‘?-’ является префиксным оператором — это мы уже знаем. А слово ‘слесарь’ постфиксным оператором, если конечно мы не забыли определить его перед использованием (предложение определения оператора в программе должно стоять до его использования!). Постфиксный оператор как и префексный имеет всего один аргумент. Предложение выше равносильно следующему предложению:
?-(слесарь(вася)). % Если приоритет оператора '?-' выше чем у 'слесарь'
или
слесарь(?-(вася)). % Если приоритет оператора 'слесарь' выше чем у '?-'
Существуют ещё инфиксные операторы, которые должны стоять в середине предложения — между своими двумя аргументами.
Распознаются операторы в предложении следующим образом. Берётся оператор с наивысшим приоритетом и ищется в предложении (если это префиксный оператор, то первое слово в предложении должно равняться этому предикату, если это постфиксный — последнее слово должно совпадать с оператором, если инфиксный — тогда в предложении, слева направо, ищется слово совпадающее с оператором). Если оператор не найден — берётся оператор у которого приоритет пониже и т.д.
Скобки среди операторов мы используем для того, когда хотим, чтобы оператор более высокого приоритета был аргументом оператора более низкого приоритета, например:
X = 5*(2+1)
Здесь оператор ‘*’ имеет более низкий приоритет, чем ‘+’, но скобки всё меняют. Теперь оператор ‘+’ благодаря скобкам имеет приоритет 0.
Заметим, что обычный терм — это, по сути, тоже оператор с нулевым приоритетом, где функтор это имя оператора, а в скобках — это древовидная структора из операторов ‘,’. Однако при обработке интерпретатором текста программы, после вторичной обработки такой структуры из запятых, они становятся аргументами заданного функтора и запятые исчезают. Тоже самое касается правил, где правая часть оператора ‘:-’ исчезает становясь телом правила.

Заключение

В конце, хочется сказать, что данное приложение работая интерпретатором и в консоли предназначено скорее для написания научно-исследовательских работ у которых код программы всегда открыт и имеется возможность улучшить или изменить программу. Пролог — это язык логического программирования, но содержит и свойства процедурного исполнения программ (бывает, важен порядок выполнения предикатов; отсечение [3]), что очень похоже на то как думает наш мозг. Поэтому он очень хорошо подходит для написания программ с искусственным интеллектом. В качестве примера, можете посмотреть программный код игры «крестики и нолики» [4], в котором для человека, который не одну программу написал на Прологе, многие места в программе будут легко понятны, да и программа маленькая.

Моя почта: elevferii_pechori@mail.ru.

Составлять статью помогал Владимир Васильев rrrfer@mail.ru.

Список литературы
1. Братко И. Программирование на языке Пролог для искусственного интеллекта.- М., 1990.
2. Введение в логическое программирование (Prolog). URL:
https://pro-prof.com/archives/2362
3. Принципы построения программ на Prolog. URL:
https://pro-prof.com/forums/topic/semantics-prolog
4. Программный код игры в «крестики и нолики» с искусственным интеллектом. URL: https://disk.yandex.ru/d/5ED-43Uveub1ew
5. Prolog Classic | Интерпретатор Пролог программ с выводом в консоль. NashStore: https://store.nashstore.ru/store/651cf1c60a39b23f221fc121
или Яндекс Диск:
https://disk.yandex.ru/d/5ED-43Uveub1ew
6. Исходный код интерпретатора Prolog-программ на Android (PrologClassic). URL:
https://disk.yandex.ru/d/AVxfoYbg07ZcnA%5Bprolog%5D

Не делайте абзацы с 4 пробелами )

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

Изменения связанные с версией 2.3:

  1. Имя предиката functor_chars/2 изменено на chars/2. Где первый аргумент теперь должен быть строго атомарным;
  2. Добавлены предикаты:
    а) fchar/3, где 1-ый аргумент - это первый символ от атомарного терма 3-го аргумента, а 2-ой аргумент - это остаток;
    б) string/3, который истинен, если первые 2-а атомарные аргумента, при слиянии имён равны имени 3-го атомарного аргумента;
    в) name/2, который истинен, если 1-ый аргумент атомарен и его имя распадается на список кодов его символов;
    г) tell/1, который истинен, если его аргумент - это атом, в котором указан путь и имя существующего файла. Предикат устанавливает данный файл как поток вывода. Если аргумент равен user потоком вывода будет консоль. Если аргументом является свободная переменная, тогда мы просто получаем путь (оно же его имя) файла текущего потока вывода;
    д) told/0, который является истинным, если текущий поток вывода является файлом, который и закрывается;
    е) delete_file/1, который истинен, если возможно удалить файл указанный в аргументе, что и делает предикат;
    ё) create_file/1, который истинен, если возможно создать файл;
    ж) инфиксный оператор =:, который истинен, если справа у нас список, у которого первый элемент - это имя терма стоящего слева от оператора, а остальные элементы (если они есть) - его аргументы.

Изменения в приложении с версией 2.4:

  1. При запуске приложения вместе с окном появляется сервисное уведомление в шторке уведомлений. Оно нужно для того, чтобы во первых, после закрытия окна его можно было восстановить с текущими данными (на андроид 8 это не работает; на 9 и 10 не проверял), тоесть даже если вы закроете окно, например, при работе программы, все данные которые программа будет выводить в консоль, после восстановления окна, появятся на экран. Во вторых, теперь из шторки уведомлений можно прервать работу программы, например при случайном зацикливании. Приложение, теперь, полностью закрывается только из шторки уведомлений;
  2. Запущенная программа, теперь будет работать и при выключенном экране (Внимание: андроид на xiaomi так устроен, что приложение сильно потребляющее ресурсы, тоесть при работе сложной программы, может быть убито, если окно свёрнуто: не закрыто, не видимо, а именно свёрнуто; имейте ввиду!);
  3. Добавлен поиск в тексте;
  4. Добавлено окно со списком всех операторов, тоесть встроенные и те, которые написаны в программе;
  5. Добавлен предикат ucase/2, у которого второй аргумент это строка с возведёнными символами в верхний регистр строки первого аргумента. Первый аргумент должен быть обязательно определён;
  6. Приложение, призапуске неправильной программы, более подробно показывает ошибку;
  7. Минимаeльные требования: андроид 8.0.

Изменения в приложении с версией 2.5:

  1. Ускорена работа редактора и интерпретатора;
  2. Переменные окрашиваются зелёным цветом;
  3. Добавлен встроенный предикат consult(F), где F - это путь и имя файла. При чтении с консоли (consult(user)), end_of_consult является меткой завершения чтения, после которой должна сразу стоять точка;
  4. Добавлен встроенный предикат lcase(A1,A2), где A2 - это строка A1 превидённая к нижнему регистру;
  5. Добавлен встроенный предикат e(E), Где E - это число e;
  6. Добавлен оператор exp (A1 exp A2), число A1 возводится в степень A2.
  7. Подобие: ?- X. - теперь работает всегда. Например:
    а) ?- X = (Y=5+3), X. Результат: X = ( 8 = 5 + 3 ), Y = 8.
    Или
    б) ?- X = not(7=5+3), X. Результат: X = not ( 7 = 5 + 3 ), потому что, предикат в переменной истинен.

Почему мы должны изучать этот prolog, а не haskell? Пролог отличается тем, что в нем есть жручая “машина логического вывода”, которую надо императивно ограничивать отсечениями перебора, иначе всё будет безбожно тормозить. Вот просто почему сразу не писать хорошо? Да ещё распараллеливаемо для современных многоядерных процессоров (которые, да, в смартфонах).

Не понимаю, что можно распараллелить в логической программе. Это не нейронные сети же, чтобы их параллелить?!

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

Согласен. Я в таких случаях запускаю ещё клон приложения. (Приложение, для тяжёлой программы, обязательно должно работать или в фоне, или в переднем окне, или в виде плавающего окна, потому что тяжёлое свёрнутое приложение miui от ксиаоми обязательно зарубит).
Так что, когда работают несколько приложений, вероятность получить стих раньше возрастает. :thinking:

Изменения в приложении с версией 3.0:

  1. Параллельное программирование:
    С новой версией приложения появился предикат ‘::-’, который является тем же правилом, но с той разницей, что его предикаты в теле правила выполняются параллельно. Для удобства, назовём его правилом П.
    Например:
    ПравилоП(X)::- предикат1(X), предикат2(X), предикат3(X).
    Здесь, все три предиката будут выполняться одновременно. Что касается переменных, если, к примеру, они есть в голове правила и далее, используются, скажем, во всех предикатах, то на момент запуска предикатов, будут использоваться переменные из заголовка тела, в каждом предикате, такими какими являются в голове правила (включая свободные переменные). И что важно, конкретизация переменных, из головы правила, в одном предикате, не повлияет на те же переменные из другого предиката (считайте их разными копиями). Но, после удачного выполнения эти копии между собой унифицируются. И только после этого, такое правило может быть удачным (декларативно - «истинным»).
    В таком правиле тоже есть механизм возврата с поиском новых решений. С декларативной стороны он выдаст, при необходимости, все возможные решения для каждого предиката, и нам не важно как он их будет искать. Но с технической стороны, это будет происходить следующим образом. Скажем, такое правило, после первого запуска, выдаст первые решения для своих предикатов. Если нужно искать следующий результат, для правила, то сначала делается откат последнего предиката (крайний справа или снизу). Если последний предикат всё равно будет ложным, тогда откатывается предпоследний предикат и т.д. Похоже на обычное правило, не так ли? Но дальше вы узнаете, что есть и разница. Если, скажем, при выполнении правила ПравилоП(Х) (см. выше), после первого решения для правила, неудачным стал предикат предикат2(X), тогда он отправляется в конец списка (после последнего удачного предиката):
    ПравилоП(X)::- предикат1(X), предикат3(X), предикат2(X).
    И если после этого, неудачными будут предикаты: предикат1(X), предикат2(X), тогда, теперь первый предикат отправится перед предикатом предикат2(X) (то-есть, после последнего удачного предиката):
    ПравилоП(X)::- предикат3(X), предикат1(X), предикат2(X).
    А если бы, с самого начала после первого удачного решения, неудачными стали бы оба предиката предикат1(X), предикат2(X), тогда они бы оба отправились в конец списка (после последнего удачного предиката предикат3(X)):
    ПравилоП(X)::- предикат3(X), предикат1(X), предикат2(X).
    Как видите, порядок отправляющихся одновременно предикатов сохраняется: слева на право.
    Все эти перемещения и порядок нужны для того, чтобы получить ВСЕ решения, как если бы они выполнялись последовательно.
    Теперь поговорим ещё об одном виде параллельного программирования - это параллельные вопросы (операторы: ‘!-’ и ‘?-’).
    Параллельные вопросы бывают двух видов:
  1. У первых предикаты выполняются точно также - последовательно, - как и у главного вопроса, за исключением, что эти параллельные вопросы выполняются одновременно с главным и параллельными вопросами. Например:
    cons(вопрос2) ?- предикат1, предикат2.
    Левая часть предиката должна обязательно состоять из функтора cons и одного атомарного аргумента, который не может быть переменной (идентификатор вопроса).
  2. У вторых предикаты выполняются параллельно, как в правилах П, ну и конечно, сами вопросы выполняются одновременно с главным и параллельными вопросами. Например:
    par(вопрос2) ?- предикат1, предикат2.
    Левая часть предиката должна обязательно состоять из функтора par и одного атомарного аргумента, который не может быть переменной (идентификатор вопроса).
    Теперь, поговорим немножко о том что будет, если нехватит ОЗУ при выполнении параллельных правил П.
    Допустим, у вас, выполняется параллельное правило правП1 с предикатами: предикат1, предикат2. Итак, предикат1 и предикат2 выполняются параллельно. Что же будет, если во время их выполнения не хватит оперативной памяти? А будет то, что в какой-то момент, один предикат будет выполняться дальше, а другой будет ждать, пока не выполнится первый. А если быть точнее, пока не освободится достаточно памяти. А поскольку надо, чтобы не просто выполнился один из предикатов, а после выполнения одного снова освободилась память, - я советую делать следующее. Допустим, вам известно, что с правиломП может такое случиться. Тогда, с каждым его предикатом (предикат1, предикат2 и т.д.) нужно сделать следующую трансформацию:
    предикат1_(M,X):- предикат1(X), assertA (p(M,X)), false; retractA (p(M,X)).
    Мы создали новый предикат предикат1_(M,X), в котором при удачном выполнении предиката предикат1(X), сохраняется результат его выполнения в факт p(M,X), где М - это уникальный идентификатор предиката (чтобы не путался факт одного предиката с фактом другого). Таким образом, после выполнения предиката предикат1(X), весь стэк освобождается до момента его выполнения, что позволяет дальше выполняться другим параллельным предикатам.
  1. Добавлены предикаты:
    а) lock_up_writeread/0. Предоставляет текущему вопросу выводить на экран или в файл или читать аналогично, не позволяя другим параллельно выполняющимся вопросам делать в этот момент тоже самое;
    б) unlock_writeread/0. Разблокировка ввода-вывода, если в вопросе, из которого он вызван, была использована блокировка (lock_up_writeread);
    в) mark/1. Используется для получения заголовка вопроса являющийся индивидуальным идентификатором. Например:
  • par(1)!- mark(M).
    M = par(1).
  • cons(2)?- mark(M).
    M = cons(2).
  • Но: ?- mark(M).
    M = ?.
    г) synchronize/1. Синхронизирует код с другими потоками (параллельными вопросами или правилами), у которого аргумент должен быть атомарным, являющийся меткой для синхронизации с другим таким же кодом. Работа его такова: когда два или несколько вопросов или параллельных предикатов доходят до этого предиката с одной и той же меткой, у одного код выполнятся дальше, остальные ждут;
    д) unsynchronize/1. Завершает синхронизацию кода с определённой меткой.

Это как-нибудь согласуется с какими-нибудь стандартами на пролог, или теперь получился несовместимый язык?

Это просто добавление. Всё что должно быть в прологе - здесь есть. Это просто добавление.
Хотите пользуйтесь - хотите нет.

Ну вообще, PrologClassic похож на SWI-Prolog. Его приемущество в том, что он работает на Андроиде.

приемущество

Это не аргумент. На андроиде надо программировать на том, на чём программируют для андроида максимальное количество людей, то есть на Java.

Аргумент изучать пролог должен крыться в каких-то уникальных возможностях, даруемых самим прологом. Но их не существует.

Но я и не думал выставлять его для создания коммерческих приложений. Не зря же он работает в консоли. Для обучения прологу разве не сгодится? SWI-Prolog , вроде, на андроиде не работает. А этот - да. Разве не удобно иметь корманное приложение, которое всегда с тобой?
Может я в статье, написал что-то лишнее, прошу прощения.

Изменения в версии 3.2:
С новой версией добавлен умный помощник (его зовут Ванечка). Этот помощник основан на базе генеративной языковой модели.


Для того, чтобы этот помощник появился нужно при первом запуске приложения или в настройках выбрать желаемую GPT на базе которой помощник будет работать (YandexGPT или GigaChat) и ввести авторизационные данные (после подключения в облаке, например Yandex Cloud) услуги работы с API генеративной модели (как это сделать - читайте документацию для соответствующей «сетки»).

Возможности помощника:

  1. может предоставить общую справочную информацию по PrologClassic и вообще по прологу, например:

  2. может предоставить справочную информацию по встроенным операторам и встроенным предикатам, например:

  3. может написать небольшой программный код (в запросе должны обязательно присутствовать ключевые слова: «программный код»), например:

Некоторые рекомендации:

  1. Касательно GigaChat Lite (в приложении обозначена как GigaChat);
  2. В чате, после запроса на написание программного кода, если нужно получить справочную информацию - нужно очистить чат, т.к. после написания кода сетка ещё будет думать об этом коде и не поймёт что вы от неё хотите.

Отличный маркетинговый материал. Жаль, что я не покупатель.