Getting started with Prolog-2

博客分类: 技术 阅读次数: comments

Getting started with Prolog-2

递归

与通常的命令式语言相对,Prolog是声明式的。

声明式语言处理集合时更多用递归而非迭代。

实现递归的方法是,在一个规则中包含若干子目标,其中一个子目标是该规则本身,这个子目标就成为递归子目标

ancestors.pdb 可以查询家族树中的祖先和后代:

尾递归优化

使用递归子目标可能耗尽栈空间。为此,声明式语言在设计时采用尾递归优化,使得处于规则末尾的递归子目标在被递归调用时能及时释放调用栈,从而保持内存占用量恒定。

所以,上面的程序需要把递给子目标放在最后。

列表,元组

列表是变长容器,而元组则是定长容器。两者都是有序的。

当你从合一的角度考虑时,列表和元组都会变得更加强大。

如果两个元组或数组的所有元素可以合一(按顺序、一一对应),则这两个元组或数组可以合一。对应位置可以是相等的常量,或者一个常量和一个变量。

两个空列表可以合一。元组不能为空。

非空列表另可通过[Head|Tail]语法提取出首个元素。

| ?- [a, b, c] = [H|T].

H = a
T = [b,c]

yes
| ?- [a, b, c] = [a|[H|T]].

H = b
T = [c]

yes
| ?- [a, b, c] = [_|[_|[T|_]]].

T = c

yes

声明式的递归:要证明则要证明

命令式语言的递归思路是,为了求一个函数的值,需要计算若干值,其中包括调用这个函数自身求得的一个值,通过递归调用直到不带递归可以求得值的情况,最终算出函数的值。

与此相对,声明式语言的递归思路是,为了使变量满足一条规则,需要满足它的若干子目标,其中一条子目标是这条规则本身,为了满足规则必须满足递归子目标,递归查询直到子目标不包含递归子目标,最终确定变量的值。

list_math.pdb

同一规则可以在两个方向上使用

规则本来是用来判断常量之间是否满足这个条件的,但是在不同位置引入变量之后就可以用来查询不同的问题。

比如上面ancestor规则既可以查询祖先,又可以查询后代。这样看来所有规则都是双向的,[Head|Tail]匹配也是双向的。

又,内置的列表append规则既可以连接两个列表,又可以从一个列表中减去另一个列表,还可以列出把列表分割成两段的不同方式。

| ?- append([a, b],[c, d], What).

What = [a,b,c,d]

yes
| ?- append([a, b], What, [a,b,c,d]).

What = [c,d]

yes
| ?- append(X, Y, [a,b,c,d]).        

X = []
Y = [a,b,c,d] ? a

X = [a]
Y = [b,c,d]

X = [a,b]
Y = [c,d]

X = [a,b,c]
Y = [d]

X = [a,b,c,d]
Y = []

(1 ms) yes

自行实现append规则:

concat.pdb

| ?- concat([1,2],[3], X).

X = [1,2,3] ? 

yes

求值过程:

  1. concat([1, 2], [3], X)
  2. 代入:concat([1 [2]], [3], [1 D]), where X = [1 D] :- 推出:concat([2], [3], D)
  3. 代入:concat([2 []], [3], [2 [3]]), where D = [1 [2]]
  4. 所以 X = [1 D] = [1, 2, 3]。

Extra

flip.pdb实现了reverse规则

这本书讲的并不能让人学会这门语言。。