递归
与通常的命令式语言相对,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
求值过程:
- concat([1, 2], [3], X)
-
代入:concat([1 [2]], [3], [1 D]), where X = [1 D] :-
推出:concat([2], [3], D) -
代入:concat([2 []], [3], [2 [3]]), where D = [1 [2]] -
所以 X = [1 D] = [1, 2, 3]。
Extra
flip.pdb
实现了reverse
规则
这本书讲的并不能让人学会这门语言。。
本站文章除注明转载/出处外由 wguosh 创作,均为本站原创或翻译,采用 知识共享署名4.0 国际许可协议进行许可
转载前请务必署名
最后编辑时间为:2021-04-12 18:41:00