I have a need to compose declarative transformations on values.
“go to this key, that index, this key, and update the value there, now go to this other key, another key and update the value there …”
“now apply this transformation” … and so on.
I’m playing around with the idea of using zippers to “move around inside” a data structure, perform changes to a focused valued, and unzipping to commit changes. A zipper just keeps track of a “focused” value and a stack of where it came from.
The following is just some code I was playing with today, and looks like it’ll give me what I need.
I’m just asking for feedback to make sure I’m not reinventing a wheel or missing something obvious.
Using DCGs for state management, we have:
state(S), [S] --> [S].
state(S0, S1), [S1] --> [S0].
zip -->
state(X, zipper(X, [])).
unzip -->
state(zipper(X, []), X).
Assuming (for now) our structures are nested assocs, we have
% step into a substructure by key
:- op(500, fx, >:).
>:(Key) -->
state(zipper(Focus, Where), zipper(Value, [Focus:Key|Where])),
{ get_assoc(Key, Focus, Value)}.
unzip -->
state(zipper(NewValue, [Owner:Key|Where]), zipper(NewOwner, Where)),
{ put_assoc(Key, Owner, NewValue, NewOwner) },
unzip.
a way to do something to the focused part of a zipper
focused(Do) -->
state(zipper(Focus, Where), Focus),
Do,
state(NewFocus, zipper(NewFocus, Where)).
and a way to “commit” to a substructure change, and start anbother
andThenZip -->
unzip, zip.
Finally we can do things like this:
% a nested assoc
example_assoc(Assoc) :-
list_to_assoc([a-A, x-X], Assoc),
list_to_assoc([b-1, c-1], A),
list_to_assoc([y-10, z-10], X).
some_update -->
zip, >:a, >:b,
focused((
state(In1, Out1), {Out1 is In1 + 1}
)),
andThenZip, >:x, >:y,
focused((
state(In2, Out2), {Out2 is Out1 + In2}
)),
unzip.
and run it to get:
?- example_assoc(A), phrase(some_update, [A], [O]).
A = t(x,t(z,10,<,t(y,10,-,t,t),t),<,t(a,t(c,1,<,t(b,1,-,t,t),t),-,t,t),t),
O = t(x,t(z,10,<,t(y,12,-,t,t),t),<,t(a,t(c,1,<,t(b,2,-,t,t),t),-,t,t),t) ;