Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
360 views
in Technique[技术] by (71.8m points)

queue - Difference lists in Prolog and mutable variables

Are difference lists a means to 'get-around' the fact that variables are immutable in prolog?

I.e. if I implement append using difference lists:

diff_append(OpenList, Hole, L2) :-
    Hole = L2.

And then run:

X=[a,b,c|Hole], diff_append(X, Hole, [d,e,f]).

The X, in a way, has been used as a mutable variable. For our intents and purposes it has been changed?

In other words, the fact that we have been able to modify X (mutable) rather than having to construct a new list, say Z (immutable) is what makes difference lists attractive. So why not just have mutable variables?

Update:

diff_append2(OpenList-Hole,L2):-
    Hole=L2.

X=[a,b,c|Ho]-Ho,diff_append2(X,[d,e,f]).
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Difference lists are rather used to get around another limitation: that you need to traverse the whole list to "append" to its end (think of a singly linked list where you have a pointer to the first element, but not to the sentinel).

In code: since a list [a, b, c] is (traditionally) the nested term .(a, .(b, .(c, []))), adding a d to the end of it means "peeling off" each term before replacing the [] at the end (the center) with .(d, []). So, to implement append/3:

append([], L, L).
append([H|T], L, [H|R]) :-
    append(T, L, R).

which is how it is traditionally implemented.

This is another answer which covers the topic quite extensively.

Something that that answer doesn't go into is that library predicates that "return" lists might offer a difference-list version of the predicate, for efficiency reasons, in case you might want to append to the end of the list. Examples would be read_pending_codes/3 and read_pending_chars/3, or the four-argument version of findall/4.

DCGs are a convenient way of working with difference lists without explicitly passing around the two arguments for the list and the tail.

Implementing a queue

The three most basic operations for a queue: making an empty queue, pushing to the back, and popping from the front:

%% 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)).

As one should notice, queue_head/3 will let you pop from the front or push to the front of the queue. queue_last/3 only lets you push to the back: once you have pushed an element, you don't have (constant time) access to the element before it in the queue.

The first argument to the queue/3 term is there to prevent what Richard O'Keefe calls "hallucinating" variables, that is, popping from a queue more elements than have been pushed to it. It is interesting to leave out that first argument from the predicates above and see what happens:

?- empty_queue(Q0), queue_head(Q0, H, Q1).
Q0 = queue([H|_G341], [H|_G341]),
Q1 = queue(_G341, [H|_G341]).

instead of failing.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...