DemonScript: Основы языка
Введение
DemonScript - это скриптовый язык для описания обыденных знаний в задачах искусственного интеллекта. В нём можно использовать стандартные алгоритмические операторы (условия, циклы, функции и т.п.). Кроме этого DemonScript содержит развитый синтаксис и алгоритмы по работе с графами, которые служат для описания объектов реального мира и отношений между ними. Наконец, DemonScript - это мощный движок проведения логических выводов в рамках интервальной логики множественных миров. Подробнее все эти подходы описаны в последующих документах, справочнике и наборе примеров готовых программ. В данном документе содержится "экспресс-введение" в язык.
Скрипт пишется в любом текстовом редакторе. Приведенные далее примеры могут быть запущены консольной утилитой ds.exe. Так, создадим файл hello.ds и напишем в нём:
out "Hello real world!" |
Переменные и операции
Переменные в DemonScript должны объявляться при помощи оператора var:
var F = 5 // вещественное число var L = True , S = "строка" // логическое значения и строка out " L = " , L // выводим строку и переменную > L = True |
Тип переменных определяется, исходя из контекста, и может меняться в процессе работы скрипта. Базовыми типами являются числа, логические значения, нечёткие числа, строки, массивы, хэш-таблицы и графы. При объявлении переменной ей сразу можно присвоить некоторое значение.
С числами можно проводить стандартные вычислительные операции:
F = (F/2) * 2.3 * Math.pow(2,3) + (F%2) + F++ |
Все базовые типы можно сравнивать друг с другом: X == Y - равенство значений, X != Y - неравенство значений. Результатом этих операций является бинарное логическое значение (True или False).
Результатом сравнения переменных разных типов (5 == "5") будет False. Логические значения можно объединять при при помощи логического И: X & Y, логического ИЛИ: X | Y, отрицания: !X и импликации: X -> Y. Можно также использовать скобки: X & (Y | ! Z ) и т.д. В простейшем случае DemonScript работает с трёзначной логикой: False, True и Undef:
var X = True out !X //> False out X & Undef //> Undef out X | Undef //> True |
Различные выражения и команды DemonScript могут находиться на одной строке. Желательно выражения разделять точкой с запятой: ";", но в ряде случаев это не обязательно. Впрочем, если обнаружилась ошибка парсинга скрипта, хорошей идеей будет поставить в этом месте разделитель между командами.
Условный оператор
Условный оператор if анализирует истинность выражения. Если оно истинно (True), то выполняется оператор, идущий после двоеточия ":". Если там должно находиться несколько операторов, то они окружаются фигурными скобками и тогда двоеточие не обязательно:
var X = True if X == True : out " ok1 " if X : out " ok2 " if X != False { X = False out " ok3 " } out X |
Так как DemonScript использует многозначную логику, кроме стандартного оператора ветвления else, существуют его многочисленные версии. Кроме этого, условные операторы можно использовать внутри выражений (см. следующий документ).
Циклы
В DemonScript существует два вида циклов: while и for. Цикл while выполняется, пока условие, идущее после оператора while истинно:
var I = 1 // начальное значение while I < 10 // повторяем, пока I < 10 { out I // выводим I I = I + 1 // увеличиваем его (можно также I++) } |
Этот же цикл можно организовать при помощи оператора for (переменная цикла не объявляется):
for I in range(1,10) : // от 1 до 9 out I |
В цикле можно использовать оператор break. Например, если в начале блока поставить строку
if I > 5: break // прервать цикл |
Цикл for также служит для перебора различных объектов. Например, это могут быть элементы массива:
var Arr = [1,2,3, "привет" , True ] // массив из пяти элементов for A in Arr: // бежим по его элементам out A // выводим их |
Кроме этого цикл for используется для перебора узлов графа и элементов хэш-таблицы (ключ-значение).
Базовая работа с графом
Основная задача DemonScript - это работа с графами. Граф состоит из узлов, соединённых направленными рёбрами различных типов. В задачах искусственного интеллекта узлы графа могут быть объектами реального мира, находящегося в сознании системы, а рёбра - различными бинарными отношениями между объектами. Эти отношения считаются истинными, ложными или имеют промежуточные значения истинности.
Имена узлов состоят из латинских букв, цифр, символа подчёркивания: _, решётки: # и могут начинаться с символа доллара: $. Например: box, $chest#1. Первым символом имени типа ребра может быть собачка: @ (но это не обязательно). Например: in, @above. Если от узла dog к узлу box проведено ребро с именем in, это означает, что объекты состоят в отношении "dog in box" (собака находится в ящике) . Имена отношений объявляются в начале скрипта при помощи оператора edges, а имена объектов объявляются в любом месте оператором nodes:
edges in // типы рёбер nodes box, chest, cat // объявляем узлы out GRAPH // выводим текущий граф (переменну GRAPH можно не указывать) |

GRAPH { box {}, cat {}, chest {} }Пока граф состоит из трёх несвязанных узлов (их имена объявлены в операторе nodes). Устанавим связь in между узлами box и chest:
box in chest; // есть направленное ребро in из box в chest out GRAPH // опять выводим граф |

GRAPH { box { in: [ chest ] }, cat { }, chest { } }В узле box появилась информация об исходящем из него ребре in. Она хранится в массиве [...] с именем ребра in, в котором перечисляются узлы, связанные с box исходящими из него рёбрами типа in. Существуют также другие форматы вывода структуры графа.
Каждое ребро графа, кроме названия (типа ребра), содержит логическое значение, которое может быть истинным True, ложным False или иметь произвольное значение интервальной логики. Например, запретим сундуку находиться в ящике, а коту в сундуке:
chest !in box; // ! - это отрицание cat in chest = False // эквивалентный способ out GRAPH |

GRAPH { box { in : [ chest ] }, cat { in : [ !chest] }, chest { in : [ !box ] } }При выводе структуры графа истинное ребро отображается в массиве просто именем объекта: chest, а ребро с "запретом" (ложное значение) помечается восклицательным знаком: !box. Значение для ребра Undef эквивалентно отсутствию ребра.
Для вывода значения ребра воспользуемся оператором out:
out cat in box, cat in chest |
Оператор for позволяет перебирать узлы графа, удовлетворяющие определённому условию. Выведем, например, всех кто не находится в сундуке (нет явной информации об этом):
for X in (X in chest) != True : out X |
Логическое программирование
При помощи графов описываются факты (объекты и отношения между ними). Используя стандартные алгоритмические методы (условия, циклы и демоны), можно создавать процедурные знания, которые из фактов выводят новые факты. Однако часто более эффективным оказывается подход, в котором формулируются аксиомы, а затем новые факты из этих аксиом выводятся "сами по себе".
Рассмотрим небольшой пример из мира закрытых ящиков, которые могут быть вложены друг в друга (подробнее см. пример "Пространственные отношения"). Определим отношение in и сформулируем его свойства при помощи аксиом, которые добавим в объект Mind методом .add:
edges in // типы отношений Mind.add( // свойства отношения in: X !in X, // антирефлексивность X in Z & Z in Y -> X in Y, // транзитивность (вложенность) Z in X & Z in Y -> X in Y | Y in X | X==Y, // образует дерево ) |
Пусть есть три ящика a, b и с. Известно, что ящик a находится внутри c, а ящик b не находится внутри c. Необходимо ответить на вопросы: находится ли b внутри a?; может ли a находиться в b?.
Cоздадим модель в которой есть три ящика и установим начальные условия задачи:
nodes a, b, c // объекты a in c; // ящик a внутри ящика c b !in c; // ящик b не внутри ящика c |
Mind.set_graph(GRAPH) // логический вывод out b in a //> False (нет) out a in b //> Undef (возможно) |
Демоны
Демон - это функция языка, которая принимает на вход значения нескольких аргументов и возвращает вычисленное значение при помощи оператора return. Для объявления демона, перед его именем ставится оператор def, а после имени демона в круглых скобках перечисляются его аргументы. Алгоритм функции окружается фигурными скобками { ... }.
Например, часть кода демона отношения in(X, Y) (объект X находится в объекте Y) имеет вид:
edges in // типы рёбер def in (X, Y) { if X == Y : // сам в себе return False // находится не может if (X in Y) != Undef : // есть ребро return X in Y // возвращаем его значение if Y in X : // наоборот, Y в X return False // не может находится } |
nodes cat, box // объявляем узлы cat in box; out in(box, cat) // находится ли ящик в коте? (False) |
Демоны также могут возвращать имена узлов или рёбер:
def who(E, X) { for Z in Z E X : return Z // первый же узел по Е из X return $UNKNOWN_NODE } out who(in, box) // кто в ящике? (cat) |
Демон должен быть объявлен до его использования. Все переменные, объявленные внутри демона, являются локальными и ниже его не видны. Более того, внутри демона не видны никакие переменные объявленные выше. Демон является "кирпичиком" процедурного знания и должен быть максимально независимым от "окружения". Впрочем, при помощи оператора global можно обойти это ограничение.
На самом деле демоны это нечто большее, чем просто функции. Например, возможен следующий код:
def in(X,Y): get : return X in Y set: return X in Y = value in(a, b) = True out in(a, b); |
Помимо демонов существует также механизм модульности при помощи подключения других файлов со скриптами:
#include "file_name.ds"