• Learn Prolog Now 翻译


    内容提要

    • 操作符的属性
    • 自定义操作符

    正如我们之前看到的,在一些特定的情况下(比如,当进行数字计算时),Prolog允许我们书写比内部表示更加友好的操作符语法。事实上,如我们将要学习到的,Prolog甚至允许我们自定义操作符。这本章中,我们将会学习操作符的属性,及其如何自定义操作符。


    操作符的属性

    首先从数字运算的例子开始。Prolog会在内部使用这样的表达式:is(11, +(2, 、*(3,3))),但是我们可以自由地在编程中将函子*和+写在参数之间,从而构成更加友好的表达式:11 is 2 + 3*3。函子能够写在参数之间被称为中缀操作符。Prolog中其他一些中缀操作符的例子是::-, -->, =, =.., ==等等。除了中缀操作符之外,还有前缀操作符(写在参数之前)和后缀操作符(写在参数之后)。比如,?-是一个前缀操作符,还有就是单独的负号-,代表负数(比如, 1 is 3+ -2)。后缀表达式的一个例子就是在C语句中的++,可以用于变量的自增。

    当我们学习Prolog的数字运算时,我们已经了解到Prolog可以消除数字运算表达式的歧义。所以当我们写 2 + 3*3的时候,Prolog知道其含义是:2 + (3*3),而不是 (2 + 3) * 3。但是Prolog是如何知道的?因为每一个操作符都有一定的优先级。+的优先级比*的优先级别更高,所以+能够成为表达式 2 + 3*3的主函子。(这里注意理解Prolog优先级高的含义和我们平时的理解不一致,优先级高在Prolog中是指越外层的函子,比如内部表达式为:+(2, *(3, 3)))。类似地,is的优先级比+的优先级高,所以 11 is 2 + 3*3 会被转换为内部的表达式:is(11, +(2, *(3, 3)))。在Prolog中,优先级使用从0到1200的数字表示;最大的数字,代表最高的优先级。给出一些例子,=的优先级是700,+的优先级是500,*的优先级是400。

    如果在一个表达式中存在多个相同优先级的操作符时会发生什么?比如之前我们说查询,2 =:= 3 == =:=(2,3)会使得Prolog报错。Prolog不知道如何解析表达式,是 =:=(2, ==(3, =:=(2,3))),还是 (=:=(2,3), =:=(2,3))?原因是因为 和 =:=有相同的优先级,而Prolog不能决定正确的括号方式。在这种情况下,显式的括号是必须要程序中提供的。


    下面的查询会如何进行?
    ?- X is 2 + 3 + 4.
    

    Prolog会困惑吗?完全不会:它工作的很愉快并且得出的正确的答案 X = 9。但是内部采用了哪种括号的方式,是 is(X, +(2, +(3,4))),还是 is(X, +(+(2,3), 4))?如下面的查询所示,Prolog选择的是第二年方式:

    ?- 2 + 3 + 4 = +(2, +(3, 4)).
    false
    
    ?- 2 + 3 + 4 = +(+(2, 3), 4).
    true
    

    这里Prolog会使用+的结合性来消除歧义:+是左结合性的,意味着+右边的表达式的优先级并且小于+本身的优先级,同时左边的表达式的优先级必须等于+本身的优先级。一个表达式的优先级简单地认为和其主操作符的优先级一致,或者当被括号括起来时为0. 3 + 4这个表达式的主操作符是+,所以将 2 + 3 + 4 转换为 +(2, +(3, 4))意味着第一个+右边的表达式的优先级和+本身一致,这是不正确的。它必须要更低才行。

    操作符==,=:=被定义为没有结合性,这意味着操作符两边的参数必须要有更低的优先级。所以 2 =:= 3 == =:=(2, 3)是一个错误的表达式,因为无论如何加括号,都会有歧义:2 =:= 3有和=相同的优先级,同时 3 == =:=(2,3)和=:=有相同的优先级。

    操作符的类型(中缀,前缀和后缀),它们的优先级,和它们的结合性是Prolog中关于操作符必须了解的知识,这样我们才能够写出符合用户使用习惯,同时满足Prolog内部表达方式的代码。


    ## 自定义操作符

    除了为特定的一些函子提供了友好的操作符语法外,Prolog也允许自定义操作符。比如,你能够定义一个后缀操作符,is_dead,Prolog允许你在知识库中写出 zed is_dead 来替代标准的 is_dead(zed)的表示方法。

    Prolog的自定义操作符看上去如下:

    :- op(Precedence, Type, Name).
    

    正如之前提及的,优先级是一个从0到1200的数字,数字越大,优先级越高(再次强调,这里的优先级高,是指函子使用在越外层,和我们平时理解的优先级高先运算和调用是相反的)。Type是一个原子,表示操作符的类型和结合性。比如+的这个原子是yfx,含义是说+是一个中缀操作符,f代表操作符,x和y代表参数。更进一步地说,x的优先级低于+的优先级,y的优先级低于或者等于+的优先级。这里有一些可能的type:

    infix xfx, xfy, yfx
    prefix fx, fy
    suffix xf, yf

    所以我们自定义的操作符is_dead代码如下:

    ?- op(500, xf, is_dead).
    

    这里有一些内置操作符的定义。可以看到相同属性的操作符定义在一个子句中,通过最后一个参数给出名字的列表:

    :- op(1200, xfx, [ :-, -->]).
    :- op(1200,  fx, [ :-, ?-]).
    :- op(1100, xfy, [;]).
    :- op(1000, xfx, [ ',' ]).
    :- op( 700, xfx, [ =, is, =.., ==, ==, =:=, ==, <, >, =<, >=]).
    :- op( 500, yfx, [ +, -]).
    :- op( 500,  fx, [ +, -]).
    :- op( 300, xfx, [ mod ]).
    :- op( 200, xfy, [ ^ ]).
    

    最后需要明确的一点是,自定义操作符不会实现操作符的功能,而只是定义如果使用操作符。即,一个自定义的操作符不会包括查询在什么情况下为真的运算,它仅仅是Prolog在语法层面的扩充。所以如果操作符is_dead想上面那样定义,并且你直接查询:zed is_dead,Prolog不会报语法有错误,但是同时会证明的目标是:is_dead(zed),这是Prolog内部的标准表示方法。所以这就是自定义操作符做的一切——只是将友好的语法转为Prolog真正内部的表示法。所以,Prolog将会如何回答这个查询呢?答案是false,因为Prolog试图证明:is_dead(zed),但是在知识库中找不到能够匹配的目标。但是,如果我们扩展知识库如下:

    :- op(500, xf, is_dead).
    
    kill(marsellus, zed).
    is_dead(X) :- kill(_, X).
    

    这时Prolog会根据知识库的事实和规则,回答true。

  • 相关阅读:
    温故知新,.NET 重定向深度分析
    修复搜狗、360等浏览器不识别SameSite=None 引起的单点登录故障
    用机器学习打造聊天机器人(二) 概念篇
    用机器学习打造聊天机器人(一) 前言
    做为GPU服务器管理员,当其他用户需要执行某个要root权限的命令时,除了告诉他们root密码,还有没有别的办法?
    Viterbi(维特比)算法在CRF(条件随机场)中是如何起作用的?
    使用t-SNE做降维可视化
    用深度学习做命名实体识别(七)-CRF介绍
    用深度学习做命名实体识别(六)-BERT介绍
    BERT论文解读
  • 原文地址:https://www.cnblogs.com/seaman-h-zhang/p/4683013.html
Copyright © 2020-2023  润新知