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

Компонента моделирования предназначена для декларативного описания модели предметной области в форме онтологии — сети из экземпляров данных (фактов) и абстрактных понятий, связанных между собой с помощью отношений. В ее основе лежит фреймовая логика — гибрид объектно-ориентированного подхода к представлению знаний и логики первого порядка. Ее основной элемент — понятие, описывающее моделируемый объект с помощью набора атрибутов. Понятие строится на основе других понятий или фактов, исходные понятия назовем родительскими, производное — дочерним. Отношения связывают значения атрибутов дочернего и родительских понятий или ограничивают их возможные значения. Я решил включить отношения в состав определения понятия, чтобы вся информация о нем находилась по возможности в одном месте. Стиль синтаксиса для определений понятий будет похож на SQL — атрибуты, родительские понятия и отношения между ними должны быть разнесены по разным секциям.

В этой публикации я хочу представить основные способы определения понятий.

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

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

Начнем с фактов


Факты представляют собой описание конкретных знаний о предметной области в виде именованного набора пар «ключ-значение»:

fact <имя факта> {
	<имя атрибута> : <значение атрибута>
	...	
}

Например:

fact  product {
	name: “Cabernet Sauvignon”,
	type: “red wine”,
	country: “Chile”
}

Имя факта может быть не уникальным, например, может быть множество продуктов с разным названием, типом и страной происхождения. Будем считать факты идентичными, если совпадают их имена, а также имена и значения их атрибутов.

Можно провести аналогию между фактами компоненты моделирования и фактами языка Prolog. Их отличие заключается только в синтаксисе. В Prolog аргументы фактов идентифицируются по их позиции, а атрибуты фактов компоненты моделирования — по имени.

Понятия


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

concept <имя понятия> <псевдоним понятия> (
		<имя атрибута> = <выражение>,
		...	
) 
from 
	<имя родительского понятия> <псевдоним родительского понятия> (
		<имя атрибута> = <выражение> 
	   	...
	 ),
            …
where <выражение отношений>

Пример определения понятия profit на основе понятий revenue и cost:

concept profit p (
	value = r.value – c.value,
	date
) from revenue r, cost c
where p.date = r.date = c.date

Определение понятия похоже по форме на SQL запрос, но вместо имени таблиц нужно указывать имена родительских понятий, а вместо возвращаемых столбцов — атрибуты дочернего понятия. Кроме того, понятие имеет имя, по которому к нему можно обращаться в определениях других понятий или в запросах к модели. Родительским понятием может быть как непосредственно понятие, так и факты. Выражение отношений в секции where – это булево выражение, которое может включать логические операторы, условия равенства, арифметические операторы, вызовы функций и др. Их аргументами могут быть переменные, константы и ссылки на атрибуты как родительских так и дочернего понятий. Ссылки на атрибуты имеют следующий формат:

<псевдоним понятия>.<имя атрибута>

По сравнению с фреймовой логикой в определении понятия его структура (атрибуты) объединена с отношениями с другими понятиями (родительские понятия и выражение отношений). С моей точки зрения это позволяет сделать код более понятным, так как вся информация о понятии собрана в одном месте. А также соответствует принципу инкапсуляции в том смысле, что детали реализации понятия скрыты внутри его определения. Для сравнения небольшой пример на языке фреймовой логики можно найти в прошлой публикации.

Выражение отношений имеет конъюнктивную форму (состоит из выражений, соединенных логическими операциями «AND») и должно включать условия равенства для всех атрибутов дочернего понятия, достаточные для определения их значений. Кроме того, в него могут входить условия, ограничивающие значения родительских понятий или связывающие их между собой. Если в секции where будут связаны между собой не все родительские понятия, то механизм логического вывода вернет все возможные комбинации их значений в качестве результата (аналогично операции FULL JOIN языка SQL).

Для удобства часть условий равенства атрибутов может быть вынесена в секции атрибутов дочернего и родительских понятий. Например, в определении понятия profit условие для атрибута value вынесено в секцию атрибутов, а для атрибута date – оставлено в секции where. Также можно перенести их и в секцию from:

concept profit p (
	value = r.value – c.value,
	date = r.date
) from revenue r, cost c (date = r.date)

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

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

Поскольку список родительских понятий и условия отношений разнесены по отдельным секциям, логический вывод будет немного отличаться от такового в Prolog. Опишу в общем виде его алгоритм. Вывод родительских понятий будет выполнен в том порядке, в котором они указаны в секции from. Поиск решения для следующего понятия выполняется для каждого частичного решения предыдущих понятий так же, как и в SLD резолюции. Но для каждого частичного решения выполняется проверка истинности выражения отношений из секции where. Поскольку это выражение имеет форму конъюнкции, то каждое подвыражение проверяется отдельно. Ели подвыражение ложно, то данное частичное решение отвергается и поиск переходит к следующему. Если часть аргументов подвыражения еще не определена (не связана со значениями), то его проверка откладывается. Если подвыражением является оператор равенства и определен только один из его аргументов, то система логического вывода найдет его значение и попытается связать его с оставшимся аргументом. Это возможно, если свободным аргументом является атрибут или переменная.

Например, при выводе сущностей понятия profit сначала будут найдены сущности понятия revenue, и, соответственно, значения его атрибутов. После чего равенство p.date = r.date = c.date в секции where даст возможность связать со значениями атрибуты date и других понятий. Когда логический поиск доберется до понятия cost, значение его атрибута date будет уже известно и будет является входным аргументом для этой ветви дерева поиска. Подробно рассказать об алгоритмах логического вывода я планирую в одной из следующих публикаций.

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

profit(value,date) :- revenue(rValue, date), cost(cValue, date), value = rValue – cValue

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

В результате логического вывода все атрибуты дочернего понятия должны быть связаны со значениями. А также выражение отношений должно быть истинно и не содержать неопределенных подвыражений. Стоит заметить, что вывод родительских понятий не обязательно должен быть успешным. Допустимы случаи, когда требуется проверить неудачу выведения родительского понятия из исходных данных, например, в операциях отрицания. Порядок родительских понятий в секции from определяет порядок обхода дерева решений. Это дает возможность оптимизировать поиск решения, начав его с тех понятий, которые сильнее ограничивают пространство поиска.

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

Допустимым считается создание нескольких понятий с одинаковым именем, но с разной реализацией, включая различный набор атрибутов. Это могут быть разные версии одного понятия, родственные понятия, которые удобно объединить под одним именем, одинаковые понятия из разных источников и т.п. При логическом выводе будут рассмотрены все существующие определения понятия, а результаты их поиска будут объединены. Несколько понятий с одинаковыми именами аналогичны правилу в языке Prolog, в котором список термов имеет дизъюнктивную форму (термы связаны операцией ИЛИ).

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


Одними из наиболее распространенных отношений между понятиями являются иерархические отношения, например «род-вид». Их особенностью является то, что структуры дочернего и родительского понятий будут очень близки. Поэтому поддержка механизма наследования на уровне синтаксиса очень важна, без нее программы будут переполнены повторяющимся кодом. При построении сети понятий было бы удобно повторно использовать как их атрибуты, так и отношения. Если список атрибутов легко расширять, сокращать или переопределять некоторые из них, то с модификацией отношений дело обстоит сложнее. Поскольку они представляют собой логическое выражение в конъюнктивной форме, то к нему легко прибавить дополнительные подвыражения. Но удаление или изменение может потребовать значительного усложнения синтаксиса. Польза от этого не так очевидна, поэтому отложим эту задачу на будущее.

Объявить понятие на основе наследования можно с помощью следующей конструкции:

concept <имя понятия> <псевдоним понятия> is 
	<имя родительского понятия> <псевдоним родительского понятия> ( 
		<имя атрибута> = <выражение>, 
		...
	 ),
	...
with <имя атрибута> = <выражение>, ...
without <имя родительского атрибута>, ...
where <выражение отношений>

Секция is содержит список наследуемых понятий. Их имена можно указать напрямую в этой секции. Или же указать полный список родительских понятий в секции from, а в is — псевдонимы только тех из них, которые будут наследоваться:

concept <имя понятия> <псевдоним понятия> is 
	<псевдоним родительского понятия>,
	…
from 
	<имя родительского понятия> <псевдоним родительского понятия> ( 
		<имя атрибута> = <выражение> 
		   ...
	 ),
            …
with <имя атрибута> = <выражение>, ...
without <имя родительского атрибута>, ...
where <выражение отношений>

Секция with позволяет расширить список атрибутов наследуемых понятий или переопределить некоторые из них, секция without — сократить.

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

Рассмотрим несколько примеров использования механизма наследования. Наследование позволяет создать понятие на основе уже существующего избавившись от тех атрибутов, которые имеют смысл только для родительского, но не для дочернего понятия. Например, если исходные данные представлены в виде таблицы, то ячейкам определенных столбцов можно дать свои имена (избавившись от атрибута с номером столбца):

concept revenue is tableCell without columnNum where columnNum = 2

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

concept resume is resumeV1 with skills = 'N/A'
concept resume is resumeV2 r with skills = r.coreSkills

Предположим, что в первой версии понятия «Резюме» не было атрибута с навыками, а во второй он назывался по-другому.

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

concept price is basicPrice with valueUSD = valueEUR * getCurrentRate('USD', 'EUR')

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

concept webPageElement is webPageLink
concept webPageElement is webPageInput

Или же создать подмножество понятия, отфильтровав часть его сущностей:

concept exceptionalPerformer is employee where performanceEvaluationScore > 0.95

Возможно также множественное наследование, при котором дочернее понятие наследует атрибуты всех родительских понятий. При наличии одинаковых имен атрибутов приоритет будет отдан тому родительскому понятию, которое находится в списке левее. Также можно решить этот конфликт вручную, явно переопределив нужный атрибут в секции with. Например, такой вид наследования был бы удобен, если нужно собрать в одной «плоской» структуре несколько связанных понятий:

concept employeeInfo is employee e, department d where e.departmentId = d.id 

Наследование без изменения структуры понятий усложняет проверку идентичности объектов. В качестве примера рассмотрим определение exceptionalPerformer. Запросы к родительскому (employee) и дочернему (exceptionalPerformer) понятиям вернут одну и ту же сущность сотрудника. Объекты, представляющие ее, будут идентичны по смыслу. У них будет общий источник данных, одинаковый список и значения атрибутов, на разное имя понятия, зависящее от того, к какому понятию был выполнен запрос. Поэтому операция равенства объектов должна учитывать эту особенность. Имена понятий считаются равными, если они совпадают или связаны транзитивным отношением наследования без изменения структуры.

Наследование — это полезный механизм, позволяющий выразить явным образом такие отношения между понятиями как «класс-подкласс», «частное-общее», «множество-подмножество». А также избавиться от дублирования кода в определениях понятий и сделать код более понятным. Механизм наследования основан на добавлении/удалении атрибутов, объединении нескольких понятий под одним именем и добавлении условий фильтрации. Никакой специальной семантики в него не вкладывается, каждый может воспринимать и применять его как хочет. Например, построить иерархию от частного к общему как в примерах с понятиями resume, price и webPageElement. Или, наоборот, от общего к частному, как в примерах с понятиями revenue и exceptionalPerformer. Это позволит гибко подстроиться под особенности источников данных.

Понятие для описания отношений


Было решено, что для удобства понимания кода и облегчения интеграции компоненты моделирования с ООП моделью, отношения дочернего понятия с родительскими должны быть встроены в его определение. Таким образом, эти отношения задают способ получения дочернего понятия из родительских. Если модель предметной области строится слоями, и каждый новый слой основан на предыдущем, это оправдано. Но в некоторых случаях отношения между понятиями должны быть объявлены отдельно, а не входить в определение одного из понятий. Это может быть универсальное отношение, которое хочется задать в общем виде и применить к разным понятиям, например, отношение «Родитель-Потомок». Либо отношение, связывающее два понятия, необходимо включить в определение обоих понятий, чтобы можно было бы найти как сущности первого понятия при известных атрибутах второго, так и наоборот. Тогда, во избежание дублирования кода отношение удобно будет задать отдельно.

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

relation <имя отношения> 
between <имя вложенного понятия> <псевдоним вложенного понятия> (
	<имя атрибута> = <выражение>,
 	 ...	
),
...
where <логическое выражение>

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

relation insideSquareRelation between square inner, square outer 
where inner.xLeft > outer.xLeft and inner.xRight < outer.xRight 
and inner.yBottom > outer.yBottom and inner.yUp < outer.yUp

Такое отношение, по сути, представляет собой обычное понятие, атрибутами которого являются сущности вложенных понятий:

concept insideSquare (
	inner = i
	outer = o												
) from square i, square o
where i.xLeft > o.xLeft and i.xRight < o.xRight 
and i.yBottom > o.yBottom and i.yUp < o.yUp

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

сoncept htmlFormElement is e 
from htmlForm f, insideSquareRelation(inner = e, outer = f), htmlElement e

При поиске решения сначала будут найдены все значения понятия htmlForm, затем они будут связаны со вложенным понятием outer отношения insideSquare и найдены значения его атрибута inner. А в конце будут отфильтрованы те значения inner, которые относятся к понятию htmlElement.

Отношению можно придать и функциональную семантику — использовать его как функцию булева типа для проверки, выполняется ли отношение для заданных сущностей вложенных понятий:

сoncept htmlFormElement is e 
from htmlElement e, htmlForm f
where  insideSquareRelation(e, f)

В отличие от предыдущего случая, здесь отношение рассматривается как функция, что повлияет на порядок логического вывода. Вычисление функции будет отложено до того момента, когда все ее аргументы будут связаны со значениями. То есть, сначала будут найдены все комбинации значений понятий htmlElement и htmlForm, а затем отфильтрованы те из них, которые не соответствуют отношению insideSquareRelation. Более подробно об интеграции логической и функциональной парадигм программирования я планирую рассказать в одной из следующих публикаций.

Теперь пришло время рассмотреть небольшой пример


Определений фактов и основных видов понятий достаточно, чтобы реализовать пример с должниками из первой публикации. Предположим, что у нас есть два файла в формате CSV, хранящих информацию о клиентах (идентификатор клиента, имя и адрес электронной почты) и счетах (идентификатор счета, идентификатор клиента, дата, сумма к оплате, оплаченная сумма).

А также имеется некая процедура, которая считывает содержимое этих файлов и преобразовывает их в набор фактов:

fact cell {
	table: “TableClients”,
	value: 1,
	rowNum: 1,
	columnNum: 1
};
fact cell {
	table: “TableClients”,
	value: “John”,
	rowNum: 1,
	columnNum: 2
};
fact cell {
	table: “TableClients”,
	value: “john@somewhere.net”,
	rowNum: 1,
	columnNum: 3
};
…
fact cell {
	table: “TableBills”,
	value: 1,
	rowNum: 1,
	columnNum: 1
};
fact cell {
	table: “TableBills”,
	value: 1,
	rowNum: 1,
	columnNum: 2
};
fact cell {
	table: “TableBills”,
	value: 2020-01-01,
	rowNum: 1,
	columnNum: 3
};
fact cell {
	table: “TableBills”,
	value: 100,
	rowNum: 1,
	columnNum: 4
};
fact cell {
	table: “TableBills”,
	value: 50,
	rowNum: 1,
	columnNum: 5
};

Для начала дадим ячейкам таблиц осмысленные имена:

concept clientId is cell where table = “TableClients” and columnNum = 1;
concept clientName is cell where table = “TableClients” and columnNum = 2;
concept clientEmail is cell where table = “TableClients” and columnNum = 3;
concept billId is cell where table = “TableBills” and columnNum = 1;
concept billClientId is cell where table = “TableBills” and columnNum = 2;
concept billDate is cell where table = “TableBills” and columnNum = 3;
concept billAmountToPay is cell where table = “TableBills” and columnNum = 4;
concept billAmountPaid is cell where table = “TableBills” and columnNum = 5;

Теперь можно объединить ячейки одной строки в единый объект:

concept client (
	id = id.value,
	name = name.value,
	email = email.value
) from clientId id, clientName name, clientEmail email
where id.rowNum = name.rowNum = email.rowNum;

concept bill (
	id = id.value,
	clientId = clientId.value,
	date = date.value,
	amountToPay = toPay.value,
	amountPaid = paid.value
) from billId id, billClientId clientId, billDate date, billAmountToPay  toPay,  billAmountPaid  paid
where id.rowNum = clientId.rowNum = date.rowNum = toPay.rowNum = paid.rowNum;

Введем понятия «Неоплаченный счет» и «Должник»:

concept unpaidBill is bill where amountToPay >  amountPaid;
concept debtor is client c where exist(unpaidBill {clientId: c.id});

Оба определения используют наследование, понятие unpaidBill является подмножеством понятия bill, debtor — понятия client. Определение понятия debtor содержит вложенный запрос к понятию unpaidBill. Подробно механизм вложенных запросов мы рассмотрим позже в одной следующих публикаций.

В качестве примера «плоского» понятия определим также понятие «Долг клиента», в котором объединим некоторые поля из понятия «Клиент» и «Счет»:

concept clientDebt (
	clientName = c.name,
	billDate = b.date,
	debt = b. amountToPay – b.amountPaid
) from unpaidBill b, client c(id = b.client); 

Зависимость между атрибутами понятий client и bill вынесена в секцию from, а зависимости дочернего понятия clientDebt — в секцию его атрибутов. При желании все они могут быть помещены в секцию where — результат будет аналогичным. Но с моей точки зрения текущий вариант более краток и лучше подчеркивает назначение этих зависимостей — определять связи между понятиями.

Теперь попробуем определить понятие злостного неплательщика, который имеет как минимум 3 неоплаченных счета подряд. Для этого понадобится отношение, позволяющее упорядочить счета одного клиента по их дате. Универсальное определение будет выглядеть следующим образом:

relation billsOrder between bill next, bill prev
where next.date > prev.date and next.clientId = prev.clientId and not exist(
    bill inBetween 
    where  next.clientId = inBetween.clientId 
    and  next.date > inBetween.date  > prev.date
);

В нем утверждается, что два счета идут подряд, если они принадлежат одному клиенту, дата одного больше даты другого и не существует другого счета, лежащего между ними. На данном этапе я не хочу останавливаться на вопросах вычислительной сложности такого определения. Но если, например, мы знаем, что все счета выставляются с интервалом в 1 месяц, то можно его значительно упростить:

relation billsOrder between bill next, bill prev
where next.date = prev.date + 1 month and next.clientId = prev.clientId;

Последовательность из 3х неоплаченных счетов будет выглядеть следующим образом:

concept unpaidBillsSequence (clientId = b1.clientId, bill1 = b1, bill2 = b2, bill3 = b3) 
from 
    unpaidBill b1, 
    billsOrder next1 (next = b1, prev = b2)
    unpaidBill b2
    billsOrder next2 (next = b2, prev = b3)
    unpaidBill b3;

В этом понятии сначала будет найдены все неоплаченные счета, затем для каждого из них с помощью отношения next1 будет найден следующий счет. Понятие b2 позволит проверить, что этот счет является неоплаченным. По этому же принципу с помощью next2 и b3 будет найден и третий неоплаченный счет подряд. Идентификатор клиента вынесен в список атрибутов отдельно, чтобы в дальнейшем облегчить связывание этого понятия с понятием клиентов:

concept hardCoreDefaulter is client c where exist(unpaidBillsSequence{clientId: c.id});

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

Краткие выводы.


Итак, я предложил три основных вида понятий компоненты моделирования гибридного языка:

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

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

Определения понятий напоминают SQL запросы — как по форме, так и по внутренней логике исполнения. Поэтому я надеюсь, что предложенный язык будет понятен разработчикам и иметь относительно низкий порог входа. А дополнительные возможности, такие как использование понятий в определениях других понятий, наследование, вынесенные отношения и рекурсивные определения позволят выйти за рамки SQL и облегчат структурирование и повторное использование кода.

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

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

Полный текст в научном стиле на английском языке доступен по ссылке: papers.ssrn.com/sol3/papers.cfm?abstract_id=3555711

Ссылки на предыдущие публикации:

Проектируем мульти-парадигменный язык программирования. Часть 1 — Для чего он нужен?
Проектируем мульти-парадигменный язык программирования. Часть 2 — Сравнение построения моделей в PL/SQL, LINQ и GraphQL
Проектируем мульти-парадигменный язык программирования. Часть 3 — Обзор языков представления знаний