Zippers for Racket
(require zippers) | package: zippers |
A "zipper" is a cursor into an immutable datatype that supports efficient updates in the immediate vicinity of the focus of the cursor, and where moving the cursor elsewhere takes time and allocation linear in the distance moved.
1 Guide
Huet’s Zipper (Huet 1997) is based on splitting a datatype into a focused substructure and the one-hole context within which the focused substructure was found. Contexts are represented as reversed lists, each element of which is analogous to a stack frame if the focus had been found by a series of recursive calls.
A general-purpose zipper abstraction
Struct properties for zipper context frames
Automated generation of context frames for structs
Zippers are represented using the struct zipper.
These zippers can traverse tree structures made up of
structs (or any other value for which the appropriate frame
structures are defined —
1.1 Implementing Zippers for New Types
Define frame structs for each direction of descent. Each frame struct type must have the prop:zipper-frame property with an appropriate hole-filling procedure.
Define descent procedures that place the appropriate frame onto a zipper’s context stack, extracting the relevant substructure to the focus. These procedures should raise an exception if they are applied to something other than a zipper focused on the right kind of value. Consider putting the descent procedure into a zipper-movement to enable more checks.
Optionally define other movements, such as left/list and right/list.
2 Reference
2.1 Zippers
struct
focus : any/c context : (list-of zipper-frame?)
Because it must be possible to reconstruct the original tree, the frames must be instances of a struct type with property prop:zipper-frame.
> (zip (list 1 2 3)) (zipper '(1 2 3) '())
> (zip '(set (list 1 2) (list 2 3))) (zipper '(set (list 1 2) (list 2 3)) '())
struct
(struct zipper-movement (move possible?))
move : (-> zipper? zipper?) possible? : (-> zipper? boolean?)
See can-move? for a friendly way to recognize whether a zipper-movement? can be applied.
All of the movements exported by this package as well as all of those generated by define-struct-zipper-frames satsify zipper-movement?.
procedure
move : zipper-movement? z : zipper?
procedure
z : zipper-not-at-top?
> (define sandwich (zip '(bread peanut-butter jam))) > sandwich (zipper '(bread peanut-butter jam) '())
> (define fillings (down/cdr sandwich)) > fillings (zipper '(peanut-butter jam) (list (pair-cdr-frame 'bread)))
> (up fillings) (zipper '(bread peanut-butter jam) '())
> fillings (zipper '(peanut-butter jam) (list (pair-cdr-frame 'bread)))
> (rebuild fillings) '(bread peanut-butter jam)
> (rebuild (edit reverse fillings)) '(bread jam peanut-butter)
procedure
(zipper-at-top? z) → boolean?
z : any/c
> sandwich (zipper '(bread peanut-butter jam) '())
> fillings (zipper '(peanut-butter jam) (list (pair-cdr-frame 'bread)))
> (zipper-at-top? fillings) #f
> (zipper-at-top? sandwich) #t
> (zipper-at-top? 'not-a-zipper) #f
procedure
(zipper-not-at-top? z) → boolean?
z : any/c
> sandwich (zipper '(bread peanut-butter jam) '())
> fillings (zipper '(peanut-butter jam) (list (pair-cdr-frame 'bread)))
> (zipper-not-at-top? fillings) #t
> (zipper-not-at-top? sandwich) #f
> (zipper-not-at-top? 'not-a-zipper) #f
procedure
(zipper-of/c c) → contract?
c : contract?
procedure
(zipper-in/c c) → contract?
c : contract?
2.2 Context Frames
procedure
(zipper-frame? obj) → boolean?
obj : any/c
syntax
(define-struct-zipper-frames [struct-name ...])
struct-name : identifier?
In particular, for each accessor named acc associated with each of the structs named in struct-name ..., a struct acc-frame is generated that has every field of struct-name except the one accessed by acc. The prop:zipper-frame structure type property will reconstruct the original instance, with the former focus replacing the field accessed by acc. Additionally, a zipper movement down/acc is generated to descend a zipper along that accessor.
This is an instance of the product rule (McBride 2001) when we construe a datatype as being the fixed point of a coproduct of functors given by structs.
> (struct branch (left value right) #:transparent) > (struct leaf () #:transparent) > (define-struct-zipper-frames branch leaf) > (define a-tree (zip (branch (branch (leaf) 12 (leaf)) 15 (leaf)))) > (zipper-focus a-tree) (branch (branch (leaf) 12 (leaf)) 15 (leaf))
> (define left-subtree (down/branch-left a-tree)) > (zipper-focus left-subtree) (branch (leaf) 12 (leaf))
> (define with-new-left-subtree (edit (match-lambda [(branch l v r) (branch l (* v v) r)]) left-subtree)) > (rebuild with-new-left-subtree) (branch (branch (leaf) 144 (leaf)) 15 (leaf))
syntax
(struct-zipper-out [struct-name ...])
struct-name : identifier?
2.3 Zippers for Pairs
struct
(struct pair-car-frame (cdr))
cdr : any/c
struct
(struct pair-cdr-frame (car))
car : any/c
procedure
p : (zipper-of/c pair?)
procedure
p : (zipper-of/c pair?)
2.4 Zippers for Proper Lists
struct
(struct list-item-frame (to-left to-right))
to-left : list? to-right : list?
procedure
(down/list-first l) → zipper?
l : (zipper-of/c pair?)
procedure
i : exact-nonnegative-integer?
procedure
(left/list z) → (zipper-in/c list-item-frame?)
z : (zipper-in/c list-item-frame?)
procedure
(right/list z) → (zipper-in/c list-item-frame?)
z : (zipper-in/c list-item-frame?)
2.5 Zippers for Immutable Sets
struct
(struct set-member-frame (other-members))
other-members : set?
procedure
(down/set-member element) → zipper-movement?
element : any/c
Bibliography
Gérard Huet. Functional Pearl: The Zipper. Journal of Functional Programming 7(5), pp. 549–554, 1997. |
Conor McBride. The Derivative of a Regular Type is its Type of One-Hole Contexts. Unpublished, available from author, 2001. |