建模组件旨在以本体的形式对域模型进行声明式描述-通过关系互连的数据实例(事实)和抽象概念的网络。它基于框架逻辑-面向对象的知识表示和一阶逻辑的混合体。它的主要元素是一个概念,它使用一组属性来描述建模对象。这个概念是在其他概念或事实的基础上构建的,最初的概念将被称为parental(派生子)...关系绑定子概念和父概念的属性值或限制其可能的值。我决定将关系包括在概念的定义中,以便将有关它的所有信息尽可能地放在一个位置。概念定义的语法样式将类似于SQL-属性,父概念以及它们之间的关系应分为不同的部分。
在本文中,我想介绍定义概念的主要方法。
首先,是通过变换父母观念而建立的观念。
其次,面向对象的样式意味着继承,这意味着您需要一种机制,该机制允许您通过继承父概念的属性和关系,扩大或缩小它们来创建概念。
第三,我认为一种机制可以用来定义同级概念之间的关系,而不必分为子级和父级概念。
现在让我们详细考虑建模组件的主要类型。
让我们从事实开始
事实以一组命名的键/值对的形式描述了有关领域的特定知识:
fact < > {
< > : < >
...
}
例如:
fact product {
name: “Cabernet Sauvignon”,
type: “red wine”,
country: “Chile”
}
事实名称可能不是唯一的,例如,可能有许多产品具有不同的名称,类型和原产国。如果事实的名称与属性的名称和值一致,我们将认为事实是相同的。
可以在建模组件中的事实和Prolog中的事实之间进行类推。它们仅在语法上有所不同。在Prolog中,事实参数由其位置标识,建模组件的事实的属性由名称标识。
概念
概念是描述抽象实体并基于其他概念和事实的结构。概念的定义包括名称,属性列表和子概念。还有一个逻辑表达式,描述了其(子概念)属性与父概念的属性之间的依赖关系,使您可以推断子概念的属性值:
concept < > < > (
< > = <>,
...
)
from
< > < > (
< > = <>
...
),
…
where < >
根据收入和成本 定义利润的示例:
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子句中并非所有父级概念都相关,则推理引擎将返回其值的所有可能组合(类似于SQL中的FULL JOIN操作)。
为方便起见,可以在子级和父级概念的“属性”部分中放置一些属性相等的条件。例如,在利润的定义中,属性的条件值将移动到“属性”部分,而对于日期属性则将其保留在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子句中关系表达式的有效性。...由于此表达式是联合形式,因此将分别测试每个子表达式。如果子表达式为假,则此部分解决方案将被拒绝,搜索将进行到下一个解决方案。如果某些子表达式参数尚未定义(不与值关联),则其验证将推迟。如果子表达式是一个相等运算符,并且仅定义了其一个自变量,则推理系统将找到其值,并尝试将其与其余自变量关联。如果free参数是属性或变量,则可能发生这种情况。
例如,当显示利润概念的实体时,将首先找到收入概念的实体以及相应属性的值。然后等式p.date = r.date = c.date在where部分中,您可以将日期属性和其他概念与值相关联。当逻辑搜索到达cost的概念时,其date属性的值将是已知的,并将成为搜索树的此分支的输入参数。我计划在下一本书中详细讨论推理算法。
与Prolog的区别在于,在Prolog规则中,所有内容都是谓词-引用其他规则以及内置的相等性,比较性谓词等。并且必须明确指定其检查顺序,例如,首先必须有两个规则,然后是变量相等:
profit(value,date) :- revenue(rValue, date), cost(cValue, date), value = rValue – cValue
将按此顺序执行它们。建模组件假定where子句中的所有条件计算都是确定性的,也就是说,它们不需要递归跳入下一个搜索分支。由于它们的计算仅取决于其参数,因此当参数绑定到值时,可以按任意顺序计算它们。
作为推论的结果,子概念的所有属性都必须与值相关联。关系的表达式也必须是真实的,并且不能包含未定义的子表达式。值得注意的是,育儿概念的推导不一定是成功的。在某些情况下,例如在求反操作中,需要检查从源数据派生父概念的失败。 “自”部分中父级概念的顺序确定遍历决策树的顺序。从更强烈限制搜索空间的那些概念开始,这使得优化搜索解决方案成为可能。
逻辑推理的任务是找到子概念属性的所有可能替换,并将它们中的每个替换为一个对象。如果这些对象的概念名称,名称和属性值匹配,则视为相同。
创建具有相同名称但具有不同实现(包括不同属性集)的多个概念被认为是可以接受的。这些可以是同一概念的不同版本,可以方便地以一个名称组合的相关概念,来自不同来源的相同概念等。在逻辑结论中,将考虑该概念的所有现有定义,并将其搜索结果合并。几个具有相同名称的概念类似于Prolog中的规则,其中术语列表具有析取形式(术语进行“或”运算)。
概念继承
概念之间最常见的关系之一是等级关系,例如属种。它们的独特之处在于,孩子和父母概念的结构将非常相似。因此,在语法级别对继承机制的支持非常重要;没有它,程序将充满重复的代码。在构建概念网络时,重用它们的属性和关系会很方便。尽管属性列表易于扩展,缩短或重新定义其中的一些属性,但修改关系的情况更加复杂。由于它们是合取形式的逻辑表达式,因此很容易向其添加其他子表达式。但是,删除或更改可能需要很大的语法复杂度。这样做的好处不是很明显因此,我们将把该任务推迟到将来。
您可以使用以下构造基于继承声明概念:
concept < > < > is
< > < > (
< > = <>,
...
),
...
with < > = <>, ...
without < >, ...
where < >
is 部分包含继承概念的列表。它们的名称可以在本节中直接指定。或者,在from部分中,在is中指定父级概念的完整列表-仅是将要继承的父级概念的别名:
concept < > < > is
< >,
…
from
< > < > (
< > = <>
...
),
…
with < > = <>, ...
without < >, ...
where < >
在与部分允许你扩大的继承概念的属性列表或覆盖其中的一些,在不段-缩短。
基于继承的概念的推理算法与上面讨论的概念相同。唯一的区别是,属性列表是基于父级概念的属性列表自动生成的,并且关系表达式通过子级和父级概念的属性相等操作进行补充。
让我们考虑使用继承机制的几个示例。继承使您可以基于现有概念创建一个概念,摆脱那些仅对父概念有意义而对子概念没有意义的属性。例如,如果源数据以表格的形式显示,则可以为某些列的单元格指定自己的名称(使用列号除去属性):
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
假设“ Resume”概念的第一个版本没有技能属性,而第二个版本则具有不同的名称。
在许多情况下,可能需要扩展属性列表。常见任务是更改属性的格式,添加功能上依赖于现有属性或外部数据的属性等。例如:
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
在不更改概念结构的情况下进行继承会使对象身份的验证复杂化。作为示例,请考虑exceptionPerformer的定义。对父(雇员)和子(exclusivePerformer)概念的查询将返回相同的雇员实体。表示它的对象的含义将相同。它们将具有相同的数据源,相同的列表和属性值(用于不同的概念名称),具体取决于查询所针对的概念。因此,对象相等性操作必须考虑到此功能。如果概念名称重合或通过传递性继承关系链接而未更改结构,则认为它们相等。
继承是一种有用的机制,用于显式表达概念之间的关系,例如类-子类,私有-公共和集合-子集。并且还消除了概念定义中的重复代码,并使代码更易于理解。继承机制基于添加/删除属性,以一个名称组合多个概念并添加过滤条件。它没有嵌入任何特殊的语义,每个人都可以根据需要感知和应用它。例如,从特定到一般构建层次结构,如在示例中使用概念resume,price和webPageElement。或者相反,从一般到特定,例如在带有收入和卓越绩效概念的示例中... 这将使您可以灵活地适应数据源的具体情况。
描述关系的概念
为了方便理解代码并促进建模组件与OOP模型的集成,决定将子概念与父概念的关系纳入其定义中。因此,这些关系定义了从父母那里获得孩子概念的方式。如果域模型是在层中构建的,并且每个新层都基于前一层,那么这是合理的。但是在某些情况下,概念之间的关系必须单独声明,并且不包含在概念之一的定义中。您可以使用通用术语定义通用关系,并将其应用于不同的概念,例如,父子关系。在两个概念的定义中都必须包含连接两个概念的关系,以便可以找到第一个概念的本质和第二个概念的已知属性,反之亦然。然后,为了避免代码重复,方便地分别设置关系。
在关系的定义中,有必要列出其中包含的概念,并设置将它们彼此连接的逻辑表达式:
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概念的所有值,然后将它们与关系innerSquare外部的嵌套概念相关联,并找到其内部属性的值。最后,将过滤与htmlElement概念相关的内部值。 还可以为该关系指定功能语义-可以将其用作布尔类型的函数来检查给定的嵌套概念实体是否满足该关系:
oncept htmlFormElement is e
from htmlElement e, htmlForm f
where insideSquareRelation(e, f)
与前面的情况不同,这里的关系被视为一个函数,这将影响推理的顺序。函数的求值将推迟到其所有参数都与值关联的那一刻为止。也就是说,首先将找到htmlElement和htmlForm概念的所有值组合,然后将那些不对应于insideSquareRelation关系的值组合过滤掉。我计划在下一本书中更详细地讨论逻辑和函数编程范例的集成。
现在是时候来看一个小例子了。
事实的定义和概念的基本类型足以使用第一个出版物中的债务人来实施示例。假设我们有两个CSV文件,分别存储有关客户(客户ID,名称和电子邮件)和发票(帐户ID,客户ID,日期,到期金额,已付款金额)的信息。
还有一个特定的过程可以读取这些文件的内容并将它们转换为一组事实:
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是概念账单的子集,债务人-客户的概念。债务人的定义包含一个用于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});
债务人示例演示了如何以声明性方式完全描述域模型。与以OOP或功能样式实现此示例相比,生成的代码非常简洁,易于理解,并且非常接近问题的自然语言描述。
简要结论。
因此,我提出了混合语言建模组件的三种主要概念:
- 在其他概念转换的基础上创建的概念;
- 继承其他概念的结构和关系的概念;
- 定义其他概念之间关系的概念。
这三种类型的概念具有不同的形式和目的,但是找到解决方案的内部逻辑是相同的,只是形成属性列表的方法不同。
概念定义在形式和内部执行逻辑方面都类似于SQL查询。因此,我希望所提出的语言对开发人员来说是可以理解的,并且入门门槛相对较低。附加功能(例如在其他概念的定义中使用概念,继承,派生关系和递归定义)将使您超越SQL,并使结构和重用代码更加容易。
与RDF和OWL不同,建模组件无法区分概念和关系-一切都是概念。与框架逻辑的语言相反,描述一个概念的结构的框架以及定义它们之间的连接的规则被组合在一起。与Prolog等传统逻辑编程语言不同,模型的主要元素是具有面向对象结构的概念,而不是具有平面结构的规则。这种语言设计可能不适用于创建大规模本体或一组规则,但是它更适合用于处理半结构化数据和集成不同的数据源。建模组件的概念与OOP模型的类非常接近,这应该有助于在应用程序代码中包括对模型的声明性描述的任务。
建模组件的描述尚未完成。在下一篇文章中,我计划讨论计算机逻辑领域的这些问题,例如布尔变量,否定和高阶逻辑的元素。然后是嵌套的概念定义,聚合和概念,它们使用给定的函数生成其实体。
可以使用以下网址 获得科学风格的英文全文:papers.ssrn.com/sol3/papers.cfm?abstract_id=3555711
链接到以前的出版物:
设计多范式编程语言。第1部分-它的作用是什么?
我们设计了一种多范式编程语言。第2部分-PL / SQL,LINQ和GraphQL中的模型构建比较
我们设计了一种多范例编程语言。第3部分-知识表示语言概述