Компонента моделирования предназначена для декларативного описания модели предметной области в форме онтологии — сети из экземпляров данных (фактов) и абстрактных понятий, связанных между собой с помощью отношений. В ее основе лежит фреймовая логика — гибрид объектно-ориентированного подхода к представлению знаний и логики первого порядка. Ее основной элемент — понятие, описывающее моделируемый объект с помощью набора атрибутов. Понятие строится на основе других понятий или фактов, исходные понятия назовем родительскими, производное — дочерним. Отношения связывают значения атрибутов дочернего и родительских понятий или ограничивают их возможные значения. Я решил включить отношения в состав определения понятия, чтобы вся информация о нем находилась по возможности в одном месте. Стиль синтаксиса для определений понятий будет похож на 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 — Обзор языков представления знаний
teology
Я тоже интересуюсь прогрессивными парадигмами.
В первой части вы писали:
"Вкратце опишу основную задачу
Она заключается в том, чтобы создать такой язык программирования, который был бы удобен как для описания модели предметной области, так и для работы с ней. Чтобы описание модели было как можно более естественным, понятным для человека и близким к спецификациям программного обеспечения. Но при этом оно должно быть частью кода на полноценном языке программирования. Для этого модель будет иметь форму онтологии и состоять из конкретных фактов, абстрактных понятий и отношений между ними. "
Для того, чтобы приблизить программирование к человеку, надо дать ему возможность неточно программировать. Давайте назовем то, о чем я собираюсь рассказать, парадигмой неточного программирования.
Итак, смотрите, я сейчас объясняю все очень образно, вот так:
Предлагаемая парадигма должна поднять работу с неточной информацией на более высокий уровень. Компилятор по-прежнему будет работать только с точной информацией (иначе никак), а язык программирования и IDE должны служить новой парадигме.
На самом деле существующие языки и среды разработки частично, но неосознанно (на мой взгляд) поддерживают парадигму неточного программирования.
Для этого используются:
Что значит "полноценная поддержка парадигмы неточного программирования"? А вот интересный вопрос! У меня есть несколько наметок, но мне кажется, что я ухватил взглядом лишь верхушку айсберга. Я проанализировал неточную информацию — в основном это информация от аналитиков, архитекторов ПО и собственные наблюдения и представления программиста. Цель — максимально формализовать и сохранить информацию в проекте, удобно изложить/отобразить и использовать для поддержки разработки. Информация зачастую неформализована, неструктурирована, слабо структурирована. Сейчас для подобной информации используются комментарии как возможность языка для организации "свалки", можно туда еще картинки, звукозаписи и видео сваливать. Надо уйти от свалки, можно же вместо закомменчивания строки кода использовать нормальные инструменты языка программирования??? А список to do в комментах? А обозначить паттерн проектирования (паттерны же у нас хорошо формализованы)?
Точная информация (которая предназначена для компилятора) — тут уже все изъезжено и изжевано.
OleksiiVoropai Автор
Да, мне эта тема тоже интересна.
Проблема в том, что языки программирования формальны, они недопускают неоднозначности. Грубо говоря, есть только один жестко заданный путь исполнения программы.
А естественные языки неоднозначны. Ингда одно и то же предложение можно понять разными способами. И опыт и здравый смысл позволяет оценить правильность того или иного варианта.
Было бы интересно найти способ сблизить формальные и неформальные языки. Это открыло бы очень много новых возможностей по автоматизации программирования и тестирования, различным компьютерным помощникам и т.п.
teology
Ну смотрите: неформальная информация имеет частные случаи — полное отсутствие информации, частичная (неполная) информация, вероятностная информация. Дублирование смысла — это синтаксический сахар, наелись уже. :-) А с противоречивой информацией не хотелось бы сталкиваться в программировании.
Ну вот смотрите: предлагаю элементы языка для частичной информации. Допустим мы хотим указать объект или функцию, можем назвать имя и т.п., но не знаем тело объекта или функции.
Мы можем использовать комментарии для того, чтобы в них написать незаконченную функцию или объект. Однако IDE не будет распознавать этот элемент и не может помочь в разработке.
Можно использовать привычные элементы языка для указания незавершенных программных конструкций, но помечать эти конструкции ключевым словом "under_construction". Все просто!
Про вероятностную информацию сложнее, но тоже можно реализовывать: мы можем указывать вероятностные распределения для переменных и выражений. Зачем? Компилятору это не надо, зато программистам важно. Пусть есть выражение в условии if, это выражение может принимать значения false/true. Допустим true вероятность 0.9, false — 0.1. Тогда очень важный момент: становится ясным, что путь выполнения программы then вероятнее (важнее), чем путь else. Это реализация идеи "царская дорога" из отечественной среды разработки ДРАКОН. Среда разработки может вместе с программистом понимать, где более важные элементы кода. Как-то так.
teology
Таким макаром можно наконец-то ввести в языки программирования элементы паттернов проектирования design_pattern, списки todo_list — это элементы, которые говорят, что в коде должно что-то быть, но неконкретизируют.
Хватит закомменчивать строки! Давайте просто ключевое слово использовать типа test_instruction, правда, я думаю, не первый я это придумал, есть же чем-то похожий элемент языка assert() в С++…
Короче, нужно больше идей для метаэлементов языков программирования!
Комментарии, кстати, я предлагаю не просто вставлять посреди кода, а привязывать его к элементам языка, указывать связь. Такое тоже не ново…
OleksiiVoropai Автор
Также можно вставлять ссылки на документацию, wiki, задачи в тасктрекере, результаты код ревью, комментарии разработчиков…
Можно в стиле аннотаций это сделать.
Но реализовать это лучше на уровне IDE. Во-первых, не будет привязки к конкретному языку, а во-вторых, все эти дополнительные конструкции потребуют удобных GUI элементов.
teology
Я вообще скептически отношусь к разделению ЯП и IDE. Все говорят, вот сейчас бы язык… Языки вообще для чего придуманы? Чтобы программист мог объяснить компилятору, что делать? С хрена два! Язык нужен, чтобы то, что сформулировал программист, можно было скормить нескольким компиляторам (возможность выбора). А так, в принципе, IDE может без языка и компилятора сгенерировать машинный код напрямую (в теории).
Человек же видит то, что отображает IDE, а не текст на некотором языке (хотя многие IDE настолько тупы).
teology
Не знаю, «неточное программирование» наверное немного некорректная формулировка. Если проанализировать «неточную» информацию в программировании, то я выделил бы две группы информации:
1. Обосновывающая информация (информация о системе заказчика)
1а. Таргет-информация (требования заказчика, обоснование программных решений). Примеры: «Было три варианта, этот лучший, потому что ...», «Наши аналитики провели исследование и выяснили, что оптимальное значение колеблется от 0.1 до 0.3, поэтому я выбрал 0.2».
1б. Факт-информация (фактические процессы у заказчика). Пример: «Кнопку 1 в лифте нажимают чаще других, поэтому обработчик событий ...».
2. Проективная информация. Примеры: «Вот код накидал, но не доделал», «Это я тут закомментил, когда выйдет новая фича, то надо будет раскомментировать».
Этот весь багаж знаний держится в голове у команды разработчиков, вроде больше никакой информации не циркулирует. Хорошо, когда они ее догадываются в комментарии записать.
Отдельно хотелось бы выделить проективное программирование. Это то, что реализуется todo_list, under_construction и т.п.
Поиграем?
Наверное надо кроме ошибок компиляции рассматривать ошибки проектирования (= предупреждение компиляции). То есть для последней строчки ошибки компиляции нет, а ошибка проектирования есть.
OleksiiVoropai Автор
Обосновывающая информация обычно хранится в confluence, jira, wiki. Проблема в том, что программисты обычно ленятся что-то писать без крайней необходимости. Их сложно будет убедить продублировать документацию в коде. Но вот связать код с документацией и таск трекерами могло бы иметь смысл. Иногда найти нужную страницу в wiki — довольно нетривиальная задача.
Проективная информация имеет довольно короткое время жизни — пока идет работа над задачей. Думаю, что в большинства случаев будет достаточно простых комментариев. Хотя, возможно, какие-то специальные комментарии-пометки IDE могла бы распознавать и обрабатывать.
teology
Я бы не исключал того, что будущее программирования не просто в переносе инфы из одной системы в другую или в создании линков между системами а-ля гипертекстовых ссылок, а в полной интеграции систем, когда конструкции в одной системе порождают конструкции в другой системе. Это может сокращать срок разработки, принесет профит.
Тот же under_construction может генерироваться из бэклога таск-трекера. В бэклоге создали новую задачу по запиливанию новой фичи поддержки многоязычного интерфейса (выше мой пример с InternationalSupport) — вот тебе пожалуйста, тимлид обозначил участки кода, подлежащие реконструкции.
Довольно смелые предложения?
Работа с кодом — дорогостоящий процесс и в то же время доходный, вспомните, кто самый богатый в мире. Так что про короткое время жизни — это может и верно, но не в этом суть.
P.S. Ещё в список видов неточной информации я отнес бы пояснительную информацию. Обосновывающая информация — это информация о внешних системах (системах пользователей, заказчиков), с которыми взаимодействует программа, а пояснительная информация, наоборот, это информация о коде, о внутренней системе. Просто иногда у программиста не хватает «оперативной памяти», чтобы разобраться в коде. Пояснительную информацию стараются минимизировать по правилу «не комментируй работу кода». Однако там, где оперативки много требуется, то можно разместить поясняющую диаграмму или какой-нибудь граф.
teology
Ещё в пару с under_construction {} нужно сделать возможность удаления некоторого валидного кода. Это такой код, который рабочий, но должен быть помечен как to_delete {} или to_exchange {} by {}. И добавить идентификатор изменения, который сгруппировал бы все планируемые изменения.
Как-то так.