Prolog 和可变变量中的差异列表
Difference lists in Prolog and mutable variables
差异列表是否是 'get-around' 序言中变量不可变这一事实的一种手段?
即如果我使用差异列表实现追加:
diff_append(OpenList, Hole, L2) :-
Hole = L2.
然后运行:
X=[a,b,c|Hole], diff_append(X, Hole, [d,e,f]).
在某种程度上,X 已被用作可变变量。为了我们的意图和目的,它已经改变了吗?
换句话说,我们已经能够修改 X(可变)而不是必须构建一个新列表,比如 Z(不可变),这一事实使差异列表具有吸引力。那么为什么不只使用可变变量呢?
更新:
diff_append2(OpenList-Hole,L2):-
Hole=L2.
X=[a,b,c|Ho]-Ho,diff_append2(X,[d,e,f]).
差异列表更适合用来绕过另一个限制:你需要遍历整个列表到 "append" 到它的末尾(想想一个单链表,你有一个指向第一个元素的指针,但不是给哨兵)。
在代码中:由于列表 [a, b, c]
是(传统上)嵌套术语 .(a, .(b, .(c, [])))
,因此在其末尾添加 d
意味着每个术语之前 "peeling off"将末尾(中间)的 []
替换为 .(d, [])
。所以,要实现 append/3
:
append([], L, L).
append([H|T], L, [H|R]) :-
append(T, L, R).
即how it is traditionally implemented。
这是another answer,涵盖了相当广泛的主题。
这个答案没有涉及的是 "return" 列表的库谓词可能会提供谓词的差异列表版本,出于效率原因,以防您可能想追加到末尾的名单。例如 read_pending_codes/3
and read_pending_chars/3
, or the four-argument version of findall/4
.
DCG 是一种使用差异列表的便捷方式,无需显式传递列表和尾部的两个参数。
实现队列
队列最基本的三个操作:空队列、推到后面、从前面弹出:
%% empty_queue(-Queue)
% make an empty queue
empty_queue(queue(0, Q, Q)).
%% queue_head(?Queue, ?Head, ?Queue0)
% Queue, with Head removed, is Queue0
queue_head(queue(s(X), [H|Q], Q0), H, queue(X, Q, Q0)).
%% queue_last(+Queue0, +Last, -Queue)
% Queue0, with Last at its back, is Queue
queue_last(queue(X, Q, [L|Q0]), L, queue(s(X), Q, Q0)).
人们应该注意到,queue_head/3
会让您从前面弹出 或 推到队列的前面。 queue_last/3
只允许你推到后面:一旦你推了一个元素,你就不能(恒定时间)访问队列中它之前的元素。
queue/3
项的第一个参数是为了防止 Richard O'Keefe 称之为 "hallucinating" 变量,即从队列中弹出的元素多于已推送到队列中的元素。有趣的是,从上面的谓词中省略第一个参数,看看会发生什么:
?- empty_queue(Q0), queue_head(Q0, H, Q1).
Q0 = queue([H|_G341], [H|_G341]),
Q1 = queue(_G341, [H|_G341]).
而不是失败。
差异列表是否是 'get-around' 序言中变量不可变这一事实的一种手段?
即如果我使用差异列表实现追加:
diff_append(OpenList, Hole, L2) :-
Hole = L2.
然后运行:
X=[a,b,c|Hole], diff_append(X, Hole, [d,e,f]).
在某种程度上,X 已被用作可变变量。为了我们的意图和目的,它已经改变了吗?
换句话说,我们已经能够修改 X(可变)而不是必须构建一个新列表,比如 Z(不可变),这一事实使差异列表具有吸引力。那么为什么不只使用可变变量呢?
更新:
diff_append2(OpenList-Hole,L2):-
Hole=L2.
X=[a,b,c|Ho]-Ho,diff_append2(X,[d,e,f]).
差异列表更适合用来绕过另一个限制:你需要遍历整个列表到 "append" 到它的末尾(想想一个单链表,你有一个指向第一个元素的指针,但不是给哨兵)。
在代码中:由于列表 [a, b, c]
是(传统上)嵌套术语 .(a, .(b, .(c, [])))
,因此在其末尾添加 d
意味着每个术语之前 "peeling off"将末尾(中间)的 []
替换为 .(d, [])
。所以,要实现 append/3
:
append([], L, L).
append([H|T], L, [H|R]) :-
append(T, L, R).
即how it is traditionally implemented。
这是another answer,涵盖了相当广泛的主题。
这个答案没有涉及的是 "return" 列表的库谓词可能会提供谓词的差异列表版本,出于效率原因,以防您可能想追加到末尾的名单。例如 read_pending_codes/3
and read_pending_chars/3
, or the four-argument version of findall/4
.
DCG 是一种使用差异列表的便捷方式,无需显式传递列表和尾部的两个参数。
实现队列
队列最基本的三个操作:空队列、推到后面、从前面弹出:
%% empty_queue(-Queue)
% make an empty queue
empty_queue(queue(0, Q, Q)).
%% queue_head(?Queue, ?Head, ?Queue0)
% Queue, with Head removed, is Queue0
queue_head(queue(s(X), [H|Q], Q0), H, queue(X, Q, Q0)).
%% queue_last(+Queue0, +Last, -Queue)
% Queue0, with Last at its back, is Queue
queue_last(queue(X, Q, [L|Q0]), L, queue(s(X), Q, Q0)).
人们应该注意到,queue_head/3
会让您从前面弹出 或 推到队列的前面。 queue_last/3
只允许你推到后面:一旦你推了一个元素,你就不能(恒定时间)访问队列中它之前的元素。
queue/3
项的第一个参数是为了防止 Richard O'Keefe 称之为 "hallucinating" 变量,即从队列中弹出的元素多于已推送到队列中的元素。有趣的是,从上面的谓词中省略第一个参数,看看会发生什么:
?- empty_queue(Q0), queue_head(Q0, H, Q1).
Q0 = queue([H|_G341], [H|_G341]),
Q1 = queue(_G341, [H|_G341]).
而不是失败。