Say we have a zipper containing [0, 1, 2, 3, 4, 5], and we're focusing on the 3. In code this will look like:
([2, 1, 0], 3, [4, 5])
Where [a, b, c] denotes a singly-linked list, with O(1) head (returning a) and tail (returning [b, c]). Notice that the first list is in reverse order.To focus on the next element, we put the currently focused element on the first list, and pull the head off the second list:
([3, 2, 1, 0], 4, [5])
And vice versa to focus on the previous element: ([1, 0], 2, [3, 4, 5])
I like to imagine this as a string of beads, with the focused element held in our fingers and the rest hanging down: 3
/ \
2 4
| |
1 5
|
0
To move the focus forwards and backwards, we move our grip to the next/previous bead.This works nicely as an immutable datastructure, since the tails of both lists can be shared (i.e. we don't need to copy the whole list, just the wrap/unwrap an element on to the head)
Zippers were first applied to lists, but have since been generalised:
- To trees: focusing on one node, and being able to move the focus up, to the left-child, or to the right child
- To having more than one focus
- To datastructures more generally, by treating them as 'derivatives' (as in calculus)
https://en.wikipedia.org/wiki/Zipper_(data_structure)
As an example, the XMonad window manager uses a zipper to keep track of the currently-focused window.
I've also used Zippers for window management, e.g. having a list of Displays, with one of them focused (accepting keyboard input); each Display containing a list of Workspaces, with one of them focused (currently shown); and each Workspace containing a list of Windows, with one of them focused (the active window). Keyboard shortcuts could shift between the previous/next Window, Workspace, or Display.