正如咱们以前看到的,在一些特定的状况下(好比,当进行数字计算时),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。code
若是在一个表达式中存在多个相同优先级的操做符时会发生什么?好比以前咱们说查询,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)的表示方法。co
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。