[mercury-users] Destructive list operations

Michael Day mikeday at yeslogic.com
Tue Jan 6 15:31:46 AEDT 2009

Hi Ralph,

> Yes, you will inevitably have this issue.  If you can abstract away
> from the list type, that would be good.  Destructively updating lists
> is likely to lead to some very hard to find bugs, IMHO.

Probably, yes; most likely bugs that will bite us several years later in 
unrelated code that we'll have trouble tracking down. Nasty.

With that in mind, I've tried writing a pure Mercury implementation of 
map_replace that is optimised for the situation in which no changes are 
made to the list, in which case it will be return as is with no copying 
or allocations. Here is the initial naive implementation:

:- pred map_replace_naive(pred(T, maybe(list(T))), list(T), list(T)).
:- mode map_replace_naive(in(pred(in, out) is det), in, out) is det.

map_replace_naive(_Pred, [], []).
map_replace_naive(Pred, [X|Xs0], Ys) :-
     Pred(X, Res),
         Res = no,
         map_replace_naive(Pred, Xs0, Ys0),
         Ys = [X|Ys0]
         Res = yes(Xs),
         map_replace_naive(Pred, Xs0, Ys0),
         Ys = Xs ++ Ys0

Clear specification, but it rebuilds the list pointlessly if no changes 
are made. The smarter implementation builds up a list of changes, then 
applies them to the original list in a separate step:

:- pred map_replace_smart(pred(T, maybe(list(T))), list(T), list(T)).
:- mode map_replace_smart(in(pred(in, out) is det), in, out) is det.

map_replace_smart(Pred, Xs, Ys) :-
     find_replacements(Pred, 0, Xs, [], Rs),
     apply_replacements(0, Rs, Xs, Ys).

:- pred find_replacements(pred(T, maybe(list(T))), int, list(T),
     assoc_list(int, list(T)), assoc_list(int, list(T))).
:- mode find_replacements(in(pred(in, out) is det), in, in,
     in, out) is det.

find_replacements(_Pred, _Index, [], Rs0, Rs) :-
     Rs = reverse(Rs0).

find_replacements(Pred, Index, [X|Xs], Rs0, Rs) :-
     Pred(X, Res),
         Res = no,
         Rs1 = Rs0
         Res = yes(Ys),
         Rs1 = [Index - Ys|Rs0]
     find_replacements(Pred, Index + 1, Xs, Rs1, Rs).

:- pred apply_replacements(int, assoc_list(int, list(T)), list(T), list(T)).
:- mode apply_replacements(in, in, in, out) is det.

apply_replacements(_Index, [], Xs, Xs).
apply_replacements(Index, [R|Rs], Xs, Ys) :-
         % this should never happen
         Xs = [],
         Ys = []
         Xs = [X|Xs0],
         ( if R = Index-NewXs then
             apply_replacements(Index+1, Rs, Xs0, Ys0),
             Ys = NewXs ++ Ys0
             apply_replacements(Index+1, [R|Rs], Xs0, Ys0),
             Ys = [X|Ys0]

As you can see it's much longer and less clear, with index manipulation 
which I particularly don't like. However, in theory it should be faster, 
if the list is rarely changed, as no copying or allocations will be 
performed in this case.

The worst case is if every element in the list changes, in which case it 
will build up a list of replacements and then apply them one by one to 
rebuild the list again. Perhaps an even smarter solution would be to 
jump out of find_replacements if Pred ever returns yes, and go back to 
the beginning and call map_replace_naive? That would waste time though 
by calling Pred again on the earlier items, for which we know it will 
return no. So probably not.

Any better ideas for how to write this purely in Mercury? Note that I'm 
assuming the replacement list returned by the predicate is fairly short, 
so that the append in apply_replacements is unlikely to be a problem.



Print XML with Prince!
mercury-users mailing list
Post messages to:       mercury-users at csse.unimelb.edu.au
Administrative Queries: owner-mercury-users at csse.unimelb.edu.au
Subscriptions:          mercury-users-request at csse.unimelb.edu.au

More information about the users mailing list