Traversing Datastructures with Zippers

This notebook is about using the "zipper" with joy datastructures. See the Zipper wikipedia entry or the original paper: "FUNCTIONAL PEARL The Zipper" by Gérard Huet

Given a datastructure on the stack we can navigate through it, modify it, and rebuild it using the "zipper" technique.

Trees

In Joypy there aren't any complex datastructures, just ints, floats, strings, Symbols (strings that are names of functions) and sequences (aka lists, aka quoted literals, aka aggregates, etc...), but we can build trees out of sequences.

In [1]:
[1 [2 [3 4 25 6] 7] 8]
[1 [2 [3 4 25 6] 7] 8]

Zipper in Joy

Zippers work by keeping track of the current item, the already-seen items, and the yet-to-be seen items as you traverse a datastructure (the datastructure used to keep track of these items is the zipper.)

In Joy we can do this with the following words:

z-down == [] swap uncons swap
z-up == swons swap shunt
z-right == [swons] cons dip uncons swap
z-left == swons [uncons swap] dip swap

Let's use them to change 25 into 625. The first time a word is used I show the trace so you can see how it works. If we were going to use these a lot it would make sense to write Python versions for efficiency, but see below.

In [2]:
[z-down [] swap uncons swap] inscribe
[z-up swons swap shunt] inscribe
[z-right roll< cons swap uncons swap] inscribe
[z-left swons [uncons swap] dip swap] inscribe
[1 [2 [3 4 25 6] 7] 8]
In [ ]:
[z-down] trace
In [4]:
[z-right] trace
[] [[2 [3 4 25 6] 7] 8] 1 • z-right
[] [[2 [3 4 25 6] 7] 8] 1 • roll< cons swap uncons swap
[[2 [3 4 25 6] 7] 8] 1 [] • cons swap uncons swap
 [[2 [3 4 25 6] 7] 8] [1] • swap uncons swap
 [1] [[2 [3 4 25 6] 7] 8] • uncons swap
 [1] [2 [3 4 25 6] 7] [8] • swap
 [1] [8] [2 [3 4 25 6] 7] • 

[1] [8] [2 [3 4 25 6] 7]
In [5]:
z-down
[1] [8] [] [[3 4 25 6] 7] 2
In [6]:
z-right
[1] [8] [2] [7] [3 4 25 6]
In [7]:
z-down
[1] [8] [2] [7] [] [4 25 6] 3
In [8]:
z-right
[1] [8] [2] [7] [3] [25 6] 4
In [9]:
z-right
[1] [8] [2] [7] [4 3] [6] 25
In [10]:
sqr
[1] [8] [2] [7] [4 3] [6] 625
In [11]:
[z-up] trace
[1] [8] [2] [7] [4 3] [6] 625 • z-up
[1] [8] [2] [7] [4 3] [6] 625 • swons swap shunt
[1] [8] [2] [7] [4 3] [625 6] • swap shunt
[1] [8] [2] [7] [625 6] [4 3] • shunt
  [1] [8] [2] [7] [3 4 625 6] • 

[1] [8] [2] [7] [3 4 625 6]
In [12]:
z-up
[1] [8] [2 [3 4 625 6] 7]
In [13]:
z-up
[1 [2 [3 4 625 6] 7] 8]

dip and infra

In Joy we have the dip and infra combinators which can "target" or "address" any particular item in a Joy tree structure.

In [14]:
[[[[[[sqr] dipd] infra] dip] infra] dip] infra
[1 [2 [3 4 390625 6] 7] 8]

If you were to trace the program you would see that about half of it is the dip and infra combinators de-quoting programs and "digging" into the subject datastructure. Instead of maintaining temporary results on the stack they are pushed into the pending expression (continuation). When sqr has run the rest of the pending expression rebuilds the datastructure.

Z

Imagine a function Z that accepts a sequence of dip and infra combinators, a quoted program [Q], and a datastructure to work on. It would effectively execute the quoted program as if it had been embedded in a nested series of quoted programs, e.g.:

   [...] [Q] [[dip] [dip] [infra] [dip] [infra] [dip] [infra]] Z
-------------------------------------------------------------------
     [...] [[[[[[[Q] dip] dip] infra] dip] infra] dip] infra

The Z function isn't hard to make.

In [15]:
clear
[sqr]
[[dip][dip][infra][dip][infra][dip][infra]]
[sqr] [[dip] [dip] [infra] [dip] [infra] [dip] [infra]]
In [16]:
[cons] step
[[[[[[[[sqr] dip] dip] infra] dip] infra] dip] infra]

To use it you need to run the resulting program with the i combinator.

In [17]:
[1 [2 [3 4 25 6] 7] 8] swap
[1 [2 [3 4 25 6] 7] 8] [[[[[[[[sqr] dip] dip] infra] dip] infra] dip] infra]
In [18]:
i
[1 [2 [3 4 625 6] 7] 8]

So let's define Z as:

Z == [cons] step i
In [19]:
[Z [cons] step i] inscribe
[1 [2 [3 4 625 6] 7] 8]

And here it is doing the thing.

In [20]:
clear
[1 [2 [3 4 25 6] 7] 8]
[sqr]
[[dip][dip][infra][dip][infra][dip][infra]]
[1 [2 [3 4 25 6] 7] 8] [sqr] [[dip] [dip] [infra] [dip] [infra] [dip] [infra]]
In [21]:
Z
[1 [2 [3 4 625 6] 7] 8]

Addressing

Because we are only using two combinators we could replace the list with a string made from only two characters.

   [...] [Q] 'ddididi' Zstr
-------------------------------------------------------------
   [...] [[[[[[[Q] dip] dip] infra] dip] infra] dip] infra

The string can be considered a name or address for an item in the subject datastructure.

Determining the right "path" for an item in a tree.

It's easy to read off (in reverse) the right sequence of "d" and "i" from the subject datastructure:

[ n [ n [ n n x ...
i d i d i d d Bingo!