Constraint Handling Rules 从入门到精通(三) - Prolog 基础
本文根据笔者2017年的一次讲座整理。
尽管CHR是一门 General Purpose 的编程语言,可以像C/C++那样用来做任何通用工作。然而,目前已有的实现,都是将CHR实现成一门嵌入式语言,就像lua那样。
CHR的语法,本身也是Prolog语法。这样做的好处是,CHR程序可以和Prolog程序混合在一起,让人毫不察觉。而对于那些不想和Prolog代码混合在一起的CHR实现来说,这似乎并没有什么好处,这些实现同样采用Prolog风格的语法。
让我们先从Prolog语法讲起,让大家有一个简单的印象即可。对于熟悉Prolog的同学,可以直接跳过这一节。
Prolog 语法基础
Prolog的语法[1]十分的简单,只有一种形式,也就是 Term 。下面具体说说。
Terms
Term分成两大类,一种是变量和不可再细分的,另一种是Compound Term。
Atoms
-
小写字母开头,字母、数字、下划线都可使用。如
abc
,a11
, … -
如果用单引号包裹,可以是任意内容(是否能跨行则看具体实现),如
'Abc111'
,'_f3'
, … -
通常用作 identifier。
Numbers
数字(包括整数和浮点数),如 3
,4
, 5.5
, …
Variables
-
变量以大写字母或者下划线开头为,如
Var
,Hello
,_Wo333
, … -
名为
_
的变量为匿名变量,通常用做占位符,不能被引用。 -
名为
_XXX
的变量,若没有被引用到,编译器也不会警告。 -
变量有两种状态(instantiatedness),分别是:
- Free:变量刚开始的状态是Free,这时候变量没有和任何一个值进行绑定。
- Ground:变量经过unification以后,变量已有值,值不可改。
Compound Term
-
所有符合
foo(xxx,xxx,...)
形态的都是Compound Term,括号里是参数。 -
如果不在意参数内容的话,"带
Arity
个参数的foo
"可以用foo/Arity
来描述,例如:f/3
,f/10
, …- Atom 可以看成是 arity 为 0。例如,
foo
可以看成是foo()
,也可以用foo/0
来描述。
- Atom 可以看成是 arity 为 0。例如,
-
[xxx,xxx,...]
叫 list 。list 也是一种 Compound Term,为了方便,给了相应的词法支持。empty list,也就是[]
,这是一个 atom 。而普通的list,像[1,2,3]
相当于[1|[2|[3|[]]]]
。[H|T]
这种结构称为 cons,H
部分称为 head,T
部分称为 tail,[H|T]
对应的 Compound Term 为'.'(H,T)
。
Unification
通俗讲,unifucation 就是通过类似解方程那的方法,让等式左右两边看起来一样。
例如,为了让p(1,t(B)) + D = p(A,C) + 4
左右两边的形态保持一致,那么求出来其中的变量的值如下。
1 | ?- p(1,t(B)) + D = p(A,C) + 4. |
把求出来的变量的值代入原等式,你会发现等式两边的形态变成了一样。
在 unify[2] 以后,式子中的所有变量,都将和具体的值绑定,变量的状态也从 free 变成 ground。当然,这个例子中的B
是个例外,这个变量不管有没有值,是什么值,都是可以的,所以这里在 unify 以后,B
仍保持 free 状态。
显示 Uniication 通过 =/2
或者 unify_with_occurs_check/2
来达成。在调用的过程中,也会自动进行 unification 。在本系列前边的文章中,例如min(X)
匹配min(1)
,之后可以得到X=1
,可以先粗浅的按照 unification 来理解[3]。
Prolog 程序的结构
Prolog不区分所谓的“数据”和“代码”。所有的代码和数据结构都只有一个形式,也就是Term。也许你看到的Prolog代码是这样的:
1 | hello :- |
“这似乎有点语法哦!”
实际上,这是一个 Compound Term[4] 。对于Prolog来说,是像下面这个样子。
1 | :-(hello,','(write('hello world'),nl)) |
在 Prolog 中,可以把任意 atom 定义成一个操作符。例如,把+
定义成中缀操作符[5]以后,3+4
就会被理解成+(3,4)
这样的 Compound Term 。Prolog 通过不同运算符的优先级和结合性,来使得代码更容易阅读。
这段程序可以理解为:调用:-/2
来定义一个叫hello
的谓词 。类似C中的“函数”,Prolog对应的叫“谓词”。谓词没有返回值,执行的结果只有 true 和 fail (类似返回值)。
Prolog 程序的执行,可以看成是一个个对谓词的调用,就这么简单。但是,Prolog 和 C 之类的有一点不同。例如,f(g(3))
对于C来说,会先调用g(3)
再把返回值丢给f
;而 Prolog 只调用 f
, 且把 g(3)
作为参数传给 f
,由 f
决定怎么处理 g(3)
。
在 Prolog 代码中嵌入 CHR 代码
由于 Prolog 语法的特性,在 Prolog 代码中嵌入 CHR 代码变得十分简单。对于流行的K.U.Leuven CHR system
来说,只需要在 Prolog 中定义两个操作符 ==>
和 <=>
,语法就搞定了。CHR编译器再把这些CHR代码编译成 Prolog 代码以后,使用上和普通的 Prolog 代码没有差别。