Welcome to Scribd, the world's digital library. Read, publish, and share books and documents. See more
Download
Standard view
Full view
of .
Look up keyword
Like this
1Activity
0 of .
Results for:
No results containing your search query
P. 1
Scrap Your Zippers

Scrap Your Zippers

Ratings: (0)|Views: 10|Likes:

More info:

Published by: Lucius Gregory Meredith on Nov 12, 2010
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as PDF, TXT or read online from Scribd
See more
See less

11/12/2010

pdf

text

original

 
Scrap Your Zippers
A Generic Zipper for Heterogeneous Types
Michael D. Adams
School of Informatics and Computing
http://www.cs.indiana.edu/~adamsmd/
Abstract
The zipper type provides the ability to efficiently edit tree-shapeddata in a purely functional setting by providing constant time editsat a focal point in an immutable structure. It is used in a number of applications and is widely applicable for manipulating tree-shapeddata structures.The traditional zipper suffers from two major limitations, how-ever. First, it operates only on homogeneous types. That is to say,every node the zipper visits must have the same type. In practice,many tree-shaped types do not satisfy this condition, and thus can-not be handled by the traditional zipper. Second, the traditional zip-per involves a significant amount of boilerplate code. A custom im-plementation must be written for each type the zipper traverses.This is error prone and must be updated whenever the type beingtraversed changes.The generic zipper presented in this paper overcomes theselimitations. It operates over any type and requires no boilerplatecode to be written by the user. The only restriction is that the typestraversed must be instances of the
Data
class from the
Scrap your  Boilerplate
framework.
Categories and Subject Descriptors
D.1.1 [
Programming Tech-niques
]: Applicative (Functional) Programming; E.1 [
 Data
]: DataStructures—Trees
General Terms
Design, Languages.
 Keywords
Generic programming, Zippers, Scrap your Boiler-plate, Heterogeneous Types
1. Introduction
The zipper type provides the ability to efficiently edit tree-shapeddata in a purely functional setting [14]. It has been used to im-plement text editors [6], file systems [15], and even window man-agers [31]. In general, the zipper is applicable whenever there is awell-defined focal point for edits. In a text editor, this focal pointis manifest as the user’s cursor. In a file system, it is manifest asthe current working directory. In a window manager, it is the win-dow with focus. Creating a zipper is a straightforward, formulaicprocess that derives from the shape of the type that the zipper tra-verses [9, 24].
© ACM, 2010. This is the author’s version of the work. It is posted here by permissionof ACM for your personal use. Not for redistribution. The definitive version will bepublished in
WGP ’09: Proceedings of the 2010 ACM SIGPLAN workshopon Generic programming
, (September 2010).
WGP ’10,
September 26, 2010, Baltimore, Maryland, USA.Copyright © 2010 ACM 978-1-4503-0251-7/10/09...$10.00
The traditional zipper suffers from two major limitations, how-ever. First, it is limited to operating over homogeneous types. Eachnode in the tree that the zipper traverses must be of the same type.Thus the traditional zipper works wonderfully for making edits toan abstract syntax tree for the lambda-calculus where everythingis an expression, but it is unable to manipulate an abstract syntaxtree for a language that, in addition to expressions, contains typeannotations or statements.The second limitation is the problem with any regularly struc-tured, formulaic piece of code: It shouldn’t be written in the firstplace! At least not by a human. Formulaic code is monotonous towrite and maintain, and the zipper must be updated every time theunderlying type changes. Tools exist to automatically generate zip-per definitions from a type (e.g., using Template Haskell [30]), butthis introduces a meta-layer to our programs that it would be betterto avoid unless really necessary.The generic zipper presented in this paper overcomes these lim-itations. It operates over any type irrespective of whether it is ho-mogeneous or not. It requires no boilerplate code, and integrateswithin the existing Haskell language without a meta-level system.The only restriction is the types the zipper traverses must be in-stances of the
Data
class from the
Scrap your Boilerplate
frame-work [20–22].This paper borrows terminology and operators from the
Scrap your Boilerplate
framework. They are explained when first used.Nonetheless, the reader might find it helpful to have some familiar-ity with that framework.Also, theinternals of the generic zipper use existential types andGADTs [26], but these do not show up in the user-level interface.Thus a basic understanding of them is necessary to understand theimplementation but not to use the generic zipper.The code for the generic zipper is available as a Haskell libraryfrom the HackageDB repository at
http://hackage.haskell.org/package/syz
.The remainder of this paper is organized as follows. Section 2reviews the traditional zipper and its implementation. Section 3 in-troduces the generic zipper and how to use it. Section 4 applies thegeneric zipper to generic traversals. Section 5 shows the implemen-tation of the generic zipper. Section 6 reviews related work. Section7 concludes.
2. Traditional Zippers
A zipper is made up of two parts: a one-hole context and a holevalue to fill the hole in the context. The hole value is the portionof the object rooted at the current position of the zipper withinthe overall object. This position is called the focus. The contextcontains the overall object but with the hole value missing. To seehow this works forthetraditional zipper wefollow thedevelopmentin Hinze and Jeuring [9] before moving on to the generic zipper.
 
down
Term
:: TermZipper Maybe TermZipperdown
Term
(Var s, c) = Nothingdown
Term
(Lambda s t
1
, c) = Just (t
1
, Lambda
1
s c)down
Term
(App t
1
t
2
, c) = Just (t
1
, App
1
c t
2
)down
Term
(If t
1
t
2
t
3
, c) = Just (t
1
, If
1
c t
2
t
3
)up
Term
:: TermZipper Maybe TermZipperup
Term
(t
1
, Root
Term
) = Nothingup
Term
(t
1
, Lambda
1
s c) = Just (Lambda s t
1
, c)up
Term
(t
1
, App
1
c t
2
) = Just (App t
1
t
2
, c)up
Term
(t
2
, App
2
t
1
c) = Just (App t
1
t
2
, c)up
Term
(t
1
, If
1
c t
2
t
3
) = Just (If t
1
t
2
t
3
, c)up
Term
(t
2
, If
2
t
1
c t
3
) = Just (If t
1
t
2
t
3
, c)up
Term
(t
3
, If
3
t
1
t
2
c) = Just (If t
1
t
2
t
3
, c)left
Term
:: TermZipper Maybe TermZipperleft
Term
(t
1
, Root
Term
) = Nothingleft
Term
(t
1
, Lambda
1
s c) = Nothingleft
Term
(t
1
, App
1
c t
2
) = Nothingleft
Term
(t
2
, App
2
t
1
c) = Just (t
1
, App
1
c t
2
)left
Term
(t
1
, If
1
c t
2
t
3
) = Nothingleft
Term
(t
2
, If
2
t
1
c t
3
) = Just (t
1
, If
1
c t
2
t
3
)left
Term
(t
3
, If
3
t
1
t
2
c) = Just (t
2
, If
2
t
1
c t
3
)right
Term
:: TermZipper Maybe TermZipperright
Term
(t
1
, Root
Term
) = Nothingright
Term
(t
1
, Lambda
1
s c) = Nothingright
Term
(t
1
, App
1
c t
2
) = Just (t
2
, App
2
t
1
c)right
Term
(t
2
, App
2
t
1
c) = Nothingright
Term
(t
1
, If
1
c t
2
t
3
) = Just (t
2
, If
2
t
1
c t
3
)right
Term
(t
2
, If
2
t
1
c t
3
) = Just (t
3
, If
3
t
1
t
2
c)right
Term
(t
3
, If
3
t
1
t
2
c) = Nothing
Figure 1.
Zipper Movement Operations for
Term
.
2.1 Implementing the Zipper
Consider this abstract syntax tree for a hypothetical language:
data 
Term= Var String| Lambda String Term| App Term Term| If Term Term Term
A zipper for this type is a pairing of a
Term
that is the holevalue with a
TermContext
that is the one-hole context. For eachrecursive child of each constructor in
Term
, the
TermContext
type has a constructor with that child missing and replaced by areference to a parent context.
type
TermZipper = (Term, TermContext)
data 
TermContext= Root
Term
| Lambda
1
String TermContext| App
1
TermContext Term| App
2
Term TermContext| If
1
TermContext Term Term| If
2
Term TermContext Term| If
3
Term Term TermContextfromZipper
Term
:: TermZipper TermfromZipper
Term
z = f z
where
f :: TermZipper Termf (t
1
, Root
Term
) = t
1
f (t
1
, Lambda
1
s c) = f (Lambda s t
1
, c)f (t
1
, App
1
c t
2
) = f (App t
1
t
2
, c)f (t
2
, App
2
t
1
c) = f (App t
1
t
2
, c)f (t
1
, If
1
c t
2
t
3
) = f (If t
1
t
2
t
3
, c)f (t
2
, If
2
t
1
c t
3
) = f (If t
1
t
2
t
3
, c)f (t
3
, If
3
t
1
t
2
c) = f (If t
1
t
2
t
3
, c)toZipper
Term
:: Term TermZippertoZipper
Term
t = (t, Root
Term
)getHole
Term
:: TermZipper TermgetHole
Term
(t, _) = tsetHole
Term
:: Term TermZipper TermZippersetHole
Term
h (_, c) = (h, c)
Figure 2.
Zipper Non-Movement Operations for
Term
.In the
Term
type:
The
If
constructor has three children, so there are three corre-sponding constructors in
TermContext
.
Likewise,
App
has two children and two corresponding con-structors in
TermContext
.
Since the traditional zipper operates over homogeneous types,the
String
argument to
Lambda
isn’t considered a child, thus
Lambda
has only one constructor in
TermContext
, namely theconstructor for when the
Term
child of 
Lambda
is missing.
Finally,
Var
has no
Term
children, so it has no correspondingconstructors in
TermContext
.Each
TermContext
contains a reference to a parent context, whichin turn points to its own parent context. These parent contextsform a chain back to the root of the overall object. The chain isterminated by the
Root
Term
constructor. Effectively, these contextsare the result of a pointer reversal. Instead of the parent pointing tothe child, the child now points to the parent.Navigating a
TermZipper
is implemented by
down
Term
,
up
Term
,
left
Term
and
right
Term
as shown in Figure 1. Moving down hap-pens by deconstructing the hole value, extracting achild object, andextending the context with the other children. The extracted childobject then becomes the new hole value.Moving up is the reverse process. The siblings objects stored inthe current context are combined with the hole value to form a newhole value, and the parent context becomes the current context.Moving left and right are very similar to each other. They eachtake the current hole and replace it with the sibling immediately toeither the left or the right.The movement operations may fail by returning
Nothing
if amovement is illegal (e.g., moving left when already at the left-most position). Another common way the zipper interface can bedefined is to ignore illegal movements and return the unchangedzipper instead of returning a
Maybe
type. Nevertheless, explicitlysignaling illegal movements provides more information to the userand is used when defining generic zipper traversals in Section 4.Finally, as shown in Figure 2, the zipper has functions forconverting a value to and from a zipper, getting the hole value,
 
and setting the hole value. With the exception of 
fromZipper
Term
,these are trivial wrapper functions manipulating the pair that formsa zipper. The
fromZipper
Term
function moves all the way to theroot context before returning the resulting value.
2.2 Using the Zipper
Tosee how a zipper is used inpractice, consider this abstract syntaxtree that defines a factorial function:
fac = Lambda "n"(If (App (App (Var "=") (Var "n")) (Var "0"))(Var "1")(App (App (Var "+") (Var "n"))(App (Var "fac")(App (Var "pred") (Var "n")))))
This definition contains a bug. The
+
operator should really bethe
*
operator. A zipper can fix this.First
toZipper
Term
creates a new zipper. That zipper starts witha focus at the root of the object so the hole will contain the originaldefinition of 
fac
.
*Main>
let
t
0
= toZipper
Term
fac*Main> getHole
Term
t
0
Lambda "n"(If (App (App (Var "=") (Var "n")) (Var "0"))(Var "1")(App (App (Var "+") (Var "n"))(App (Var "fac")(App (Var "pred") (Var "n")))))
The first step to getting to the offending
*
is to use
down
Term
. Nowthe hole contains the body of the
Lambda
.
*Main>
let
Just t
1
= down
Term
t
1
*Main> getHole
Term
t
1
If (App (App (Var "=") (Var "n")) (Var "0"))(Var "1")(App (App (Var "+") (Var "n"))(App (Var "fac")(App (Var "pred") (Var "n"))))
Moving down again focuses the zipper on thetest part of the
If
, butwe want to go to the third child of the
If
, so we also move using
right
Term
twice.
*Main>
let
Just t
2
= down
Term
t
1
*Main> getHole
Term
t
2
App (App (Var "=") (Var "n")) (Var "0")*Main>
let
Just t
3
= right
Term
t
2
*Main> getHole
Term
t
3
Var "1"*Main>
let
Just t
4
= right
Term
t
3
*Main> getHole
Term
t
4
App (App (Var "+") (Var "n"))(App (Var "fac")(App (Var "pred") (Var "n")))
The zipper moves down again into the function position of the
App
.
*Main>
let
Just t
5
= down
Term
t
4
*Main> getHole
Term
t
5
App (Var "+") (Var "n")
The zipper moves down one last time to get to the
Var "+"
that wewant to change to
Var "*"
.
*Main>
let
Just t
6
= down
Term
t
5
*Main> getHole
Term
t
6
Var "+"
Finally,
setHole
Term
changes the value.
*Main>
let
t
7
= setHole
Term
(Var "*") t
6
By moving up one level, we can see that our change is reflected inthe larger term.
*Main>
let
Just t
8
= up
Term
t
7
*Main> getHole
Term
t
8
App (Var "*") (Var "n")
After moving up a few more times we can retrieve the correcteddefinition of 
fac
.
*Main>
let
Just t
9
= up
Term
t
8
*Main>
let
Just t
10
= up
Term
t
9
*Main>
let
Just t
11
= up
Term
t
10
*Main> getHole
Term
t
11
Lambda "n"(If (App (App (Var "=") (Var "n")) (Var "0"))(Var "1")(App (App (Var "*") (Var "n"))(App (Var "fac")(App (Var "pred") (Var "n")))))
Or we can use
fromZipper
Term
to go directly to the root and getthe corrected definition.
*Main> fromZipper
Term
t
7
Lambda "n"(If (App (App (Var "=") (Var "n")) (Var "0"))(Var "1")(App (App (Var "*") (Var "n"))(App (Var "fac")(App (Var "pred") (Var "n")))))
3. Using the Generic Zipper
While the traditional zipper works fine for homogeneous types like
Term
, it runs into problems withmore complex types. Consider thistype representing a department:
data 
Dept = D Manager [Employee]
deriving
(Show, Typeable, Data)
data 
Employee = E Name Salary
deriving
(Show, Typeable, Data)
type
Salary = Float
type
Manager = Employee
type
Name = String
Instead of just a single type in the case of 
Term
, a zipper forthis has to traverse five different types:
Dept
,
Employee
,
Salary
,
Manager
, and
Name
. Situations like this happen often. A compilermayhave anabstract syntax treethat represents statements and typeannotations in addition to expressions, or a graphical user interfacetoolkit may represent different classes of widgets with differenttypes. The traditional zipper cannot handle this. Furthermore, foreach new type, the traditional zipper requires a complete rewrite of the boilerplate code in Figures 1 and 2.

You're Reading a Free Preview

Download
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->