DemonScript: Связывание графов


Связывание графов

Создадим граф Sen, узлы которого будут хранить классы абстрактных смыслов $container, $box и $cat:

var Sen = GRAPH("Sen")             // создаём пустой граф
GRAPH = Sen                        // делаем его текущим

nodes $container, $box, $cat       // добавляем в него узлы

$box isa $container;               // ребро isa (ящик это контейнер)

Граф Sen создаётся при помощи конструктора GRAPH(). Предопределённое отношение isa отмечает, что класс всех ящиков $box является подмножеством класса всех контейнеров $container.

Создадим ещё один граф G1. Его узлы будут означать конкретные экземпляры абстрактных классов. Пусть, например, в сознании системы находятся два конкретных ящика box1 и box2:

edges in

var G1 = GRAPH("G1")               // создаём пустой граф
GRAPH = G1                         // делаем его текущим
nodes box1, box2                   // и добавляем в него узлы

box1 in box2;                      // box1 находится в box2

Объявим эти ящики экземплярами класса всех ящиков $box:

[box1, box2] isa Sen.$box;
Узел Sen.$box означает узел $box в графе Sen. Так, выше между узлом box1 графа G1 проведено ребро типа isa в узел $box внешнего графа Sen. Таким образом, конструкция:
   переменная_графа . имя_узла
даёт доступ к узлу графа, который сейчас не активен (на который не ссылается GRAPH).


Связанные и несвязанные узлы

Создадим копию графа G1

var G2 = G1.copy("G2")
Графы G1 и G2 принадлежат одному классу, поэтому имеют общие именованные узлы box1 и box2. По умолчанию, имя узла ссылается на узел того графа, который сейчас активен (является текущим):
GRAPH = G2                         // текущий граф G2
box1 !in box2;                     // меняем значение отношения в G2
Если необходимо получить доступ к ребру неактивного графа используется точка
out G1.box1 in G1.box2;          //> True  (это граф G1)
out box1 in box2;                  //> False (это граф G2)

Таким образом, необходимо различать имена узлов не привязанные к конкретному графу (использующие текущий граф) и именованные узлы связанные с конкретным графом:

var X = box1, Y = G1.box1, Z = G2.box1
out X, Y, Z                                       //> box1 G1.box1 G2.box1
Выше в переменных Y и Z хранятся имена узлов конкретных графов, а в X - имя узла любого графа данного класса.

Если узел привязан к графу, этот граф от него можно "отвязать" при помощи метода .unbind(). И наоборот, не привязанный к графу узел, можно привязать к текущему графу методом bind:

X = X.bind();  Y = Y.unbind();  Z = Z.unbind();
out X, Y, Z                                       //> G2.box1 box1 box1
Выше узел, хранящийся в переменной X, "привязался" к графу G2, так как он является текущим (выше стоит GRAPH = G2).


Циклы по нескольким графами

Цикл for по узлам графа перебирает узлы активного (текущего) графа. Внутри этого цикла можно изменить текущий граф и проходить вторым циклом по его узлам:

GRAPH = G1                         // текщий граф G1
for X {                            // идём по узлам G1
   out X
   GRAPH = Sen                     // текущий граф Sen
   for S {                         // идём по узлам Sen
      out S
      if G1.X isa S : out "ok"    // важно G1.X !!!
   }
}
out GRAPH.name()                   //> Sen
Необходимо иметь ввиду два важных момента. Во вложенном цикле активным графом является граф Sen. Поэтому в операторе if запись X isa S будет неверной (возникнет ошибка "current graph from different class"). Чтобы её избежать, необходимо связать имя узла и граф (G1.X). Так как внутри цикла текущий граф меняется, после цикла он также будет не совпадать с текущим графом до цикла (последняя строка выведет Sen, а не G1).


Перемещение по связанным графам

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

def isa(X,Y): return X.path(isa, Y) == True

GRAPH = G1
out isa(box1, Sen.$container)    //> True
out isa(box1, Sen.$box)          //> True
out isa(box1, Sen.$cat)          //> False
Так как демон состоит из одной команды, для его описания не обязательны фигурные скобки (но необходимо поставить двоеточие). Он вызывает для текущего графа функцию path от узла X к узлу Y по рёбрам isa. При вызове этого демона в качестве узла Y выступают узлы графа Sen, по которому продолжается поиск пути. В частности, так как в графе G истинно box1 isa Sen.$box, а в графе Sen: $box isa $container, то мы получаем, что истинно и box1 isa Sen.$box.

Заметим, что для любого узла доступен метод X.isa(Y), который работает как введенный выше демон.


Атрибуты сущностей

Узлы графов являются сущностями (конкретными или абстрактными). Каждая сущность может иметь определённые свойства (атрибуты). Кроме значения, каждый атрибут должен иметь название ("тип"), которое содержит в себе семантику (смысл) атрибута. Типы, в свою очередь, могут иметь единицы измерений, ограничения значений и т.п. Типы атрибутов, обычно, перечисляются в отдельном графе, подобном графу Sen. Например, пусть человек характеризуется возрастом и именем:

var Sen = GRAPH("Sen");
Sen.nodes 
   $age, $name;                    // названия атрибутов    
Создадим граф сознания системы из одного объекта - конкретного человека:
var G = GRAPH("G");
GRAPH = G;
nodes John;
Зададим для этого человека его возраст, вес и имя:
John[Sen.$age]    = 25;           // задаём атрибуты
John[Sen.$name]   = "Smith";

out "age=", John[Sen.$age];       //> 25  (выводим значение атрибута)

Атрибуты хранятся как значения узлов специального атрибутного графа. Этот граф сопровождает любой граф и может быть получен его методом .attr(). Узлы атрибутного графа по отношению isa связаны с графом Sen для идентификации смысла данного атрибута. В свою очередь, узлы графа G, ссылаются на узлы его атрибутного графа при помощи предопределённых рёбер attr:

out G
out G.attr()
   G {
      John { attr:[G_ATTR.1(25),G_ATTR.2("Smith")] }
   }

   G_ATTR {
      1 { val: 25,        isa: [Sen.$age]   },      
      2 { val: "Smith",   isa: [Sen.$name]   }   
   }

Атрибутный граф есть у каждого графа. Когда графы создаются по методу .copy все атрибуты копируются в новый атрибутный граф и там могут независимым образом изменяться.


Наследование отношений

В графе абстрактных классов хранятся общие знания о свойствах и параметрах различных объектов. Например, пусть мы хотим задать, что человек является субъектом (отношение sub) таких действий, как идти (goto), брать (take) и таскать (drag). А его подмножество класс всех женщин таскать не умеет (исключение).

var S = GRAPH("S");                // граф абстрактных классов
GRAPH = S;                         // делаем его текущим

nodes $human, $woman, $head,       // классы объектов
      $goto,  $take, $drag, $fly,  // классы действий
      $weight,$kilo;               // классы атрибутов
            
$woman isa $human;                 // женщина является человеком

$human  has $head;                 // человек имеет голову
$human  sub [$take, $goto, $drag]  // человек может быть субъектом действий
$woman !sub $drag;                 // женщина не может быть субъектом действия

$woman[$weight] = 50;              // типичный вес женщины - 50

$weight unit $kilo;                // вес измеряется в килограммах
Создадим теперь граф конкретных сущностей, узлом которого будет женщина Мия:
var G = GRAPH("G");                // граф конкретных сущностей
GRAPH = G;                         // делаем его тещим

nodes Mia;                         // Мия 
Mia isa S.$woman;                 // Мия является представителем класса женщин
Так как Мия является женщиной, она наследует свойства (отношения) абстрактных классов $woman и $human. Они получаются при помощи метода X.get(E, Y). Он возвращает True, если у X или его isa-предков есть ребро E в узел Y
out  S.$human.get(sub, S.$take); //> True ( $human sub $take)
out  S.$woman.get(sub, S.$take); //> True ( $woman isa $human; $human sub $take)
Аналогично для Мии:
out  Mia.get(sub, S.$take);       //> True
out  Mia.get(sub, S.$drag);       //> False
out  Mia.get(sub, S.$fly);        //> False
Так как Мия женщина, для которой отношение $take sub $woman ложно, именно его (как ближайшее в иерархии isa) возвращает метод .get.

Вызов get(E) без второго аргумента даёт массив узлов в которые направлено ребро E с любым значением истинности. Вызов же функции без аргументов, даёт хэш-таблицу всех унаследованных по isa свойств:

out  Mia.get(sub);                 //> [S.$take,S.$goto]
out  Mia.get();                    //> {has:[],obj:[],sub:[S.$take,S.$goto],unit:[]}


Наследование атрибутов

Аналогично можно наследовать атрибуты при помощи метода узла .attr():

var W = Mia.attr(S.$weight)        //> W=50 (типичный вес женщин)
out Mia.attr()                     //> [S.$weight] (все атрибуты)

Mia[S.$weight] = W                 //> задаём вес Мии

out S.$weight.nodes(unit)          //> [$kilo] (единицы измерения веса)
Метод X.attr(Y) поднимается по isa иерархии от узла X вверх, пока не встретит потомка, имеющего атрибут типа Y. Его значение и возвращает метод. Затем, это значение можно присвоить как атрибут конкретной сущности (выше это объект Mia). Вызов метода без аргументов даёт массив всех возможных типов атрибутов, которыми обладают все isa-предки. Естественно, для числового атрибута веса типичного представителя класса лучше использовать нечёткие числа.