DemonScript: Демоны


Демон как функция

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

def pow(X,Y) : return Math.pow(X,Y)  // объявляем демона
out pow(2,3)                         // вызываем  демона  > 8

Если демон не возвращает значение оператором return, то по умолчанию оно считается логическим со значением Undef. Это же произойдёт, если после оператора return нет никакого значения. Один и тот же демон может возвращать значения различных типов.

При работе с логическим значениями можно использовать дополнительный оператор return ifdef val, который производит возврат из демона, только если val != Undef:

def f()
{
   var X = Undef
   return ifdef X
   out "Hi"
}

f()
Этот демон выведет на экран Hi, так как оператор return не сработает. Если же, например, X=False, то return сработает и до оператора out демон не доберётся.


Глобальные переменные

Все объявленные в демоне переменные являются локальными. Доступа к глобальным переменным (стоящим выше объявления демона) внутри демона по умолчанию нет. Предполагается, что демоны являются элементарными процедурными "кирпичиками", поэтому они должны быть максимально независимы от "окружения". Тем не менее, внутри демона можно получить доступ к глобальным переменным при помощи оператора global:

var X = 0                          // переменная со значением 0

def fun()
{
   global X                        // разрешён доступ к глобальной переменной X
   X = 1                           // меняем значение глобальной переменной
}

fun()                              // вызываем демона
out X                              //> 1
Другой способ доступа к глобальным переменным, это оператор global в основном коде. Объявленные в нём переменные будут доступны во всех демонах:
var X = 1                          // переменная со значением 1
global X                           // будет доступна в любом демоне

def a() : out X++                  
def b() : out X++                  

a()                                //> 1 
b()                                //> 2
out X                              //> 3


Статические переменные

Оператор static объявляет внутри демона статическую переменную, значение которой будет сохраняться после окончания работы демона. Инициализация её начальным значением будет доступна при первом вызове демона:

def f()
{
   static I = 0;
   out I
   I++;
}

f()                                //> 0
f()                                //> 1
f()                                //> 2


Операторы set и get

Ключевое отличие демонов от обычных функций, это их способность не только возвращать значение, но и "принимать" его в себя. В таких демонах используются ключевые слова get и set. Пусть в графе есть ребро типа in. Определим это отношение в виде предиката:

def in(X,Y): 
   get: return X in Y 
   set: return X in Y = value
Теперь можно задать значение предиката и затем получить его:
in(a, b) = True                    // передаём демону значение 
out in(a, b)                       // демон возвращает значение

В общем случае в подобных демонах могут быть три секции:

def name()
{
    ... Эти команды выполнятся в любом случае
set:
    ... Эти команды выполняются при присваивании значения value
get:
    ... Эти команды выполняются при вызове демона
}
Таким образом, команды, идущие в начале, выполняются независимо от того, вызывается демон на чтение или запись. Блоки get и set дополнительно окружать фигурными скобками не нужно. То значение, которое присваивается в демон, находится в зарезервированной переменной value.


Рекурсия

Демоны могут вызывать других демонов и самих себя. Например, следующая функция является простым скриптовым аналогом встроенной функции поиска пути path на графе между двумя узлами X и Y по рёбрам E:

def path(E, X, Y)
{
   if X == Y :                     // нашли узел
      return True
      
   for Z in X E Z :                // для всех исходящих из X
      return path(E, Z, Y)         // продолжаем поиск
      
   return False                    // путь не нашли
}
На самом деле, этот алгоритм осуществляет поиск в глубину и корректно работает только для древесных графов.


Демоны и Mind

Демонов можно использовать в аксиомах объекта Mind. Если демон не содержит блока set, то он в аксиому только "поставляет" необходимое логическое значение. Если же set есть, то Mind будет изменять значение демона. Приведём пример вычисления числа размещения ящиков, задав отношение @in при помощи демона:

edges @in                                         // тип ребра
GRAPH.add_nodes(5)                                // граф с 5-ю узлами

def in(X,Y) :                                     // оборачиваем отношение @in демоном
   get : return X @in Y
   set : return X @in Y = value

Mind.add(                                         // аксиомы отношения @in
  !in(X,X),                                  
   in(X,Z) & in(Z,Y) -> in(X,Y),      
   in(Z,X) & in(Z,Y) -> in(X,Y) | in(Y,X) | (X==Y),
)

out "Count of graphs: ", Mind.count_models(GRAPH, @in)

Mind.out()