Visitors Unchained
Usingvisitors
to traverseabstract syntax with binding
François Pottier
Inria Paris May 22, 2017
Visitors
Installation & configuration
Installation:
o p a m u p d a t e
o p a m i n s t a l l v i s i t o r s
To configure ocamlbuild, add this in_tags:
t r u e : \
p a c k a g e ( v i s i t o r s . ppx ) , \ p a c k a g e ( v i s i t o r s . r u n t i m e )
To configure Merlin, add this in.merlin:
PKG v i s i t o r s . ppx PKG v i s i t o r s . r u n t i m e
An “iter” visitor
Annotating a type definition with[@@deriving visitors { ... }]...
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " i t e r " }]
c l a s s v i r t u a l [ ’ s e l f ] i t e r = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . i t e r
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in ()
m e t h o d v i s i t _ E A d d env c0 c1 = l e t r0 = s e l f # v i s i t _ e x p r env c0 in l e t r1 = s e l f # v i s i t _ e x p r env c1 in ()
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
... causes avisitor classto be auto-generated.
An “iter” visitor
Annotating a type definition with[@@deriving visitors { ... }]...
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " i t e r " }]
c l a s s v i r t u a l [ ’ s e l f ] i t e r = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . i t e r
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in ()
m e t h o d v i s i t _ E A d d env c0 c1 = l e t r0 = s e l f # v i s i t _ e x p r env c0 in l e t r1 = s e l f # v i s i t _ e x p r env c1 in ()
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
... causes avisitor classto be auto-generated.
An “iter” visitor
Annotating a type definition with[@@deriving visitors { ... }]...
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " i t e r " }]
c l a s s v i r t u a l [ ’ s e l f ] i t e r = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . i t e r
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in ()
m e t h o d v i s i t _ E A d d env c0 c1 = l e t r0 = s e l f # v i s i t _ e x p r env c0 in l e t r1 = s e l f # v i s i t _ e x p r env c1 in ()
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
... causes avisitor classto be auto-generated.
one method per data constructor
An “iter” visitor
Annotating a type definition with[@@deriving visitors { ... }]...
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " i t e r " }]
c l a s s v i r t u a l [ ’ s e l f ] i t e r = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . i t e r
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in ()
m e t h o d v i s i t _ E A d d env c0 c1 = l e t r0 = s e l f # v i s i t _ e x p r env c0 in l e t r1 = s e l f # v i s i t _ e x p r env c1 in ()
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
... causes avisitor classto be auto-generated.
one method per data type
An “iter” visitor
Annotating a type definition with[@@deriving visitors { ... }]...
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " i t e r " }]
c l a s s v i r t u a l [ ’ s e l f ] i t e r = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . i t e r
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in ()
m e t h o d v i s i t _ E A d d env c0 c1 = l e t r0 = s e l f # v i s i t _ e x p r env c0 in l e t r1 = s e l f # v i s i t _ e x p r env c1 in ()
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
... causes avisitor classto be auto-generated.
an environment is pushed down
An “iter” visitor
Annotating a type definition with[@@deriving visitors { ... }]...
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " i t e r " }]
c l a s s v i r t u a l [ ’ s e l f ] i t e r = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . i t e r
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in ()
m e t h o d v i s i t _ E A d d env c0 c1 = l e t r0 = s e l f # v i s i t _ e x p r env c0 in l e t r1 = s e l f # v i s i t _ e x p r env c1 in ()
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
... causes avisitor classto be auto-generated.
default behavior is to do nothing
An “iter” visitor
Annotating a type definition with[@@deriving visitors { ... }]...
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " i t e r " }]
c l a s s v i r t u a l [ ’ s e l f ] i t e r = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . i t e r
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in ()
m e t h o d v i s i t _ E A d d env c0 c1 = l e t r0 = s e l f # v i s i t _ e x p r env c0 in l e t r1 = s e l f # v i s i t _ e x p r env c1 in ()
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
... causes avisitor classto be auto-generated.
behavior at type intis inherited
A “map” visitor
There are several varieties of visitors:
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " map " }]
c l a s s v i r t u a l [ ’ s e l f ] map = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . map
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in E C o n s t r0
m e t h o d v i s i t _ E A d d env c0 c1 = l e t r0 = s e l f # v i s i t _ e x p r env c0 in l e t r1 = s e l f # v i s i t _ e x p r env c1 in E A d d ( r0 , r1 )
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
default behavior is to rebuild a tree
a “map” visitor is requested
Using a “map” visitor
Inherita visitor class andoverrideone or more methods:
l e t add e1 e2 = (* A s m a r t c o n s t r u c t o r . *) m a t c h e1 , e2 w i t h
| E C o n s t 0 , e
| e , E C o n s t 0 - > e
| _ , _ - > E A d d ( e1 , e2 )
l e t o p t i m i z e : e x p r - > e x p r = l e t v = o b j e c t ( s e l f )
i n h e r i t [ _ ] map
m e t h o d! v i s i t _ E A d d env e1 e2 = add
( s e l f # v i s i t _ e x p r env e1 ) ( s e l f # v i s i t _ e x p r env e2 ) e n d in
v # v i s i t _ e x p r ()
This addition-optimization pass isunchangedif more expression forms are added.
A “reduce” visitor
Here is another variety:
t y p e e x p r =
| E C o n s t of ( int [ @ o p a q u e ] )
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " r e d u c e " }]
c l a s s v i r t u a l [ ’ s e l f ] r e d u c e = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . r e d u c e
m e t h o d v i s i t _ E C o n s t env c0 =
l e t s0 = (f u n t h i s - > s e l f # z e r o ) c0 in s0
m e t h o d v i s i t _ E A d d env c0 c1 = l e t s0 = s e l f # v i s i t _ e x p r env c0 in l e t s1 = s e l f # v i s i t _ e x p r env c1 in s e l f # p l u s s0 s1
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
a “reduce” visitor is requested
results combined usingzero/plus
A “reduce” visitor
Here is another variety:
t y p e e x p r =
| E C o n s t of ( int [ @ o p a q u e ] )
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " r e d u c e " }]
c l a s s v i r t u a l [ ’ s e l f ] r e d u c e = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . r e d u c e
m e t h o d v i s i t _ E C o n s t env c0 =
l e t s0 = (f u n t h i s - > s e l f # z e r o ) c0 in s0
m e t h o d v i s i t _ E A d d env c0 c1 = l e t s0 = s e l f # v i s i t _ e x p r env c0 in l e t s1 = s e l f # v i s i t _ e x p r env c1 in s e l f # p l u s s0 s1
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
@opaquesubtrees are not visited
A “reduce” visitor
Here is another variety:
t y p e e x p r =
| E C o n s t of ( int [ @ o p a q u e ] )
| E A d d of e x p r * e x p r
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " r e d u c e " }]
c l a s s v i r t u a l [ ’ s e l f ] r e d u c e = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . r e d u c e
m e t h o d v i s i t _ E C o n s t env c0 =
l e t s0 = (f u n t h i s - > s e l f # z e r o ) c0 in s0
m e t h o d v i s i t _ E A d d env c0 c1 = l e t s0 = s e l f # v i s i t _ e x p r env c0 in l e t s1 = s e l f # v i s i t _ e x p r env c1 in s e l f # p l u s s0 s1
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d ( c0 , c1 ) - >
s e l f # v i s i t _ E A d d env c0 c1 e n d
class is polymorphic in env and monoid
look Ma, full type inference!
Using a “reduce” visitor
Inheritthe visitor,inherita monoid,overrideone or more methods:
l e t s i z e : e x p r - > int = l e t v = o b j e c t
i n h e r i t [ _ ] r e d u c e as s u p e r
i n h e r i t [ _ ] V i s i t o r s R u n t i m e . a d d i t i o n _ m o n o i d m e t h o d! v i s i t _ e x p r env e =
1 + s u p e r # v i s i t _ e x p r env e e n d in
v # v i s i t _ e x p r ()
This size computation remainsunchangedif more expression forms are added.
What we have seen so far
I Several built-in varieties:iter,map,endo,reduce,mapreduce,fold
I Arity two, too:iter2,map2,reduce2,mapreduce2,fold2
I Monomorphicvisitor methods,polymorphicvisitor classes
I All typesinferred
Support for parameterized data types
We wish to traverse parameterized data types, too.
I But: how does one traverse a subtree of type’a?
Two approaches are supported:
I declare avirtual visitor methodvisit_’a
I ’ais treated as a fixed/unknown type, not really as a parameter
I pass afunctionvisit_’ato every visitor method.
I allows / requires methods to be polymorphic in’a
I more compositional
In this talk: monomorphic generated methods, polymorphic hand-written methods.
A visitor for a parameterized type
Here is a “monomorphic-method” visitor for a parameterized type:
t y p e ’ i n f o e x p r _ n o d e =
| E C o n s t of int
| E A d d of ’ i n f o e x p r * ’ i n f o e x p r a n d ’ i n f o e x p r =
{ i n f o : ’ i n f o ; n o d e : ’ i n f o e x p r _ n o d e } [ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " map " }]
c l a s s v i r t u a l [ ’ s e l f ] map = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . map
m e t h o d v i r t u a l visit_ ’ i n f o : _ m e t h o d v i s i t _ E C o n s t = ...
m e t h o d v i s i t _ E A d d = ...
m e t h o d v i s i t _ e x p r _ n o d e = ...
m e t h o d v i s i t _ e x p r env t h i s =
l e t r0 = s e l f # visit_ ’ i n f o env t h i s . i n f o in l e t r1 = s e l f # v i s i t _ e x p r _ n o d e env t h i s . n o d e in { i n f o = r0 ; n o d e = r1 }
e n d
The type ofvisit_’infois’env -> ’info1 -> ’info2.
A visitor for a parameterized type
Here is a “monomorphic-method” visitor for a parameterized type:
t y p e ’ i n f o e x p r _ n o d e =
| E C o n s t of int
| E A d d of ’ i n f o e x p r * ’ i n f o e x p r a n d ’ i n f o e x p r =
{ i n f o : ’ i n f o ; n o d e : ’ i n f o e x p r _ n o d e } [ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " map " }]
c l a s s v i r t u a l [ ’ s e l f ] map = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . map
m e t h o d v i r t u a l visit_ ’ i n f o : _ m e t h o d v i s i t _ E C o n s t = ...
m e t h o d v i s i t _ E A d d = ...
m e t h o d v i s i t _ e x p r _ n o d e = ...
m e t h o d v i s i t _ e x p r env t h i s =
l e t r0 = s e l f # visit_ ’ i n f o env t h i s . i n f o in l e t r1 = s e l f # v i s i t _ e x p r _ n o d e env t h i s . n o d e in { i n f o = r0 ; n o d e = r1 }
e n d
a virtual method is declared / used
The type ofvisit_’infois’env -> ’info1 -> ’info2.
Using a visitor for a parameterized type
This visitor can mapundecoratedexpressions todecoratedexpressions:
l e t n u m b e r ( e : _ e x p r ) : int e x p r = l e t v = o b j e c t
i n h e r i t [ _ ] map v a l m u t a b l e c o u n t = 0
m e t h o d visit_ ’ i n f o _ e n v _ i n f o = l e t c = c o u n t in c o u n t < - c + 1; c e n d in
v # v i s i t _ e x p r () e
and vice-versa:
l e t s t r i p ( e : _ e x p r ) : u n i t e x p r = l e t v = o b j e c t
i n h e r i t [ _ ] map
m e t h o d visit_ ’ i n f o _ e n v _ i n f o = () e n d in
v # v i s i t _ e x p r () e
The visitor class ispolymorphicin’env,’info1and’info2.
A “mapreduce” visitor for a parameterized type
Here is another variety of visitor for this parameterized type:
t y p e ’ i n f o e x p r _ n o d e =
| E C o n s t of int
| E A d d of ’ i n f o e x p r * ’ i n f o e x p r a n d ’ i n f o e x p r =
{ i n f o : ’ i n f o ; n o d e : ’ i n f o e x p r _ n o d e }
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " m a p r e d u c e " }]
c l a s s v i r t u a l [ ’ s e l f ] m a p r e d u c e = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . m a p r e d u c e
m e t h o d v i r t u a l visit_ ’ i n f o : _ m e t h o d v i s i t _ E C o n s t = ...
m e t h o d v i s i t _ E A d d env c0 c1 =
l e t r0 , s0 = s e l f # v i s i t _ e x p r env c0 in l e t r1 , s1 = s e l f # v i s i t _ e x p r env c1 in E A d d ( r0 , r1 ) , s e l f # p l u s s0 s1
m e t h o d v i s i t _ e x p r _ n o d e = ...
m e t h o d v i s i t _ e x p r = ...
e n d
Every method returns apairof a subtree and a summary.
a “mapreduce” visitor is requested
Using a visitor for a parameterized type
This visitor canannotateevery subexpressionwith its size:
l e t a n n o t a t e ( e : _ e x p r ) : int e x p r = l e t v = o b j e c t
i n h e r i t [ _ ] m a p r e d u c e as s u p e r
i n h e r i t [ _ ] V i s i t o r s R u n t i m e . a d d i t i o n _ m o n o i d m e t h o d! v i s i t _ e x p r env { i n f o = _ ; n o d e } =
l e t node , s i z e = s u p e r # v i s i t _ e x p r _ n o d e env n o d e in l e t s i z e = s i z e + 1 in
{ i n f o = s i z e ; n o d e } , s i z e m e t h o d visit_ ’ i n f o _ e n v _ i n f o =
a s s e r t f a l s e (* n e v e r c a l l e d *) e n d in
l e t e , _ = v # v i s i t _ e x p r () e in e
Visiting preexisting types
Lists can be visited, too.
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r l i s t
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " i t e r " }]
c l a s s v i r t u a l [ ’ s e l f ] i t e r = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . i t e r
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in ()
m e t h o d v i s i t _ E A d d env c0 =
l e t r0 = s e l f # v i s i t _ l i s t s e l f # v i s i t _ e x p r env c0 in ()
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d c0 - >
s e l f # v i s i t _ E A d d env c0 e n d
a preexisting parameterized type
Visiting preexisting types
Lists can be visited, too.
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r l i s t
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " i t e r " }]
c l a s s v i r t u a l [ ’ s e l f ] i t e r = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . i t e r
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in ()
m e t h o d v i s i t _ E A d d env c0 =
l e t r0 = s e l f # v i s i t _ l i s t s e l f # v i s i t _ e x p r env c0 in ()
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d c0 - >
s e l f # v i s i t _ E A d d env c0 e n d
visitor method is passed a visitor function
Visiting preexisting types
Lists can be visited, too.
t y p e e x p r =
| E C o n s t of int
| E A d d of e x p r l i s t
[ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " i t e r " }]
c l a s s v i r t u a l [ ’ s e l f ] i t e r = o b j e c t ( s e l f : ’ s e l f ) i n h e r i t [ _ ] V i s i t o r s R u n t i m e . i t e r
m e t h o d v i s i t _ E C o n s t env c0 = l e t r0 = s e l f # v i s i t _ i n t env c0 in ()
m e t h o d v i s i t _ E A d d env c0 =
l e t r0 = s e l f # v i s i t _ l i s t s e l f # v i s i t _ e x p r env c0 in ()
m e t h o d v i s i t _ e x p r env t h i s = m a t c h t h i s w i t h
| E C o n s t c0 - >
s e l f # v i s i t _ E C o n s t env c0
| E A d d c0 - >
s e l f # v i s i t _ E A d d env c0 e n d
visitor method is inherited
Predefined visitor methods
The classVisitorsRuntime.mapoffers this method:
c l a s s [ ’ s e l f ] map = o b j e c t ( s e l f ) (* One of m a n y p r e d e f i n e d m e t h o d s : *) m e t h o d p r i v a t e v i s i t _ l i s t : ’ env ’ a ’ b .
( ’ env - > ’ a - > ’ b ) - > ’ env - > ’ a l i s t - > ’ b l i s t
= f u n f env xs - >
m a t c h xs w i t h
| [] - >
[]
| x :: xs - >
l e t x = f env x in
x :: s e l f # v i s i t _ l i s t f env xs e n d
This method ispolymorphic, so multiple instances oflistare not a problem.
Visitors – a summary
Although they follow fixed patterns, visitors are quiteversatile.
They are like higher-order functions, only morecustomizableandcomposable.
More fun with visitors:
I visitors foropen data typesand their fixed points(link);
I visitors forhash-consed data structures(link);
I iteratorsout of visitors(link).
In the remainder of this talk:
I Can we traverseabstract syntax with binding?
Visitors Unchained
Dealing with binding
Can a visitor traverseabstract syntax with bindingconstructs?
Can this be done in amodularway?
Exactly whichseparation of concernsshould one enforce?
I There are manybinding constructs,
I there are evencombinator languagesfor describing binding structure!
I and many commonoperationson terms,
I often specific of onerepresentationof names and binders,
I sometimes specific oftwosuch representations, e.g.,conversions.
I Can we insulate theend userfrom this complexity? We suggest distinguishingthreeprincipals...
Dealing with binding
Can a visitor traverseabstract syntax with bindingconstructs?
Can this be done in amodularway?
Exactly whichseparation of concernsshould one enforce?
I There are manybinding constructs,
I there are evencombinator languagesfor describing binding structure!
I and many commonoperationson terms,
I often specific of onerepresentationof names and binders,
I sometimes specific oftwosuch representations, e.g.,conversions.
I Can we insulate theend userfrom this complexity? We suggest distinguishingthreeprincipals...
Dealing with binding
Can a visitor traverseabstract syntax with bindingconstructs?
Can this be done in amodularway?
Exactly whichseparation of concernsshould one enforce?
I There are manybinding constructs,
I there are evencombinator languagesfor describing binding structure!
I and many commonoperationson terms,
I often specific of onerepresentationof names and binders,
I sometimes specific oftwosuch representations, e.g.,conversions.
I Can we insulate theend userfrom this complexity? We suggest distinguishingthreeprincipals...
Dealing with binding
Can a visitor traverseabstract syntax with bindingconstructs?
Can this be done in amodularway?
Exactly whichseparation of concernsshould one enforce?
I There are manybinding constructs,
I there are evencombinator languagesfor describing binding structure!
I and many commonoperationson terms,
I often specific of onerepresentationof names and binders,
I sometimes specific oftwosuch representations, e.g.,conversions.
I Can we insulate theend userfrom this complexity?
We suggest distinguishingthreeprincipals...
end user
end user
operations specialist
end user
operations specialist
binder maestro
The end user
Desiderata
The end user wishes:
I to describe the structure of ASTs in a concise anddeclarativestyle,
I not to be bothered with implementation details,
I possibly to have access toseveral representationsof names,
I to get access to a toolkit ofready-made operationson terms.
Example: abstract syntax of the λ -calculus
Let the type(’bn, ’term) absbe a synonym for’bn * ’term.
The end user defines his syntax as follows:
t y p e ( ’ bn , ’ fn ) t e r m =
| T V a r of ’ fn
| T L a m b d a of ( ’ bn , ( ’ bn , ’ fn ) t e r m ) abs
| T A p p of ( ’ bn , ’ fn ) t e r m * ( ’ bn , ’ fn ) t e r m [ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " map " ;
a n c e s t o r s = [ " B i n d i n g F o r m s . map " ] }]
t y p e r a w _ t e r m = ( string , s t r i n g ) t e r m t y p e n o m i n a l _ t e r m = ( A t o m . t , A t o m . t ) t e r m t y p e d e b r u i j n _ t e r m = ( unit , int ) t e r m
He getsmultiple representationsof names.
I At least two are used in any single application. (Parsing. Printing.) He getsvisitorsfor free. The methodvisit_absis used at abstractions.
I iter,map,iter2needed in practice. Focusing onmapin this talk.
Example: abstract syntax of the λ -calculus
Let the type(’bn, ’term) absbe a synonym for’bn * ’term.
The end user defines his syntax as follows:
t y p e ( ’ bn , ’ fn ) t e r m =
| T V a r of ’ fn
| T L a m b d a of ( ’ bn , ( ’ bn , ’ fn ) t e r m ) abs
| T A p p of ( ’ bn , ’ fn ) t e r m * ( ’ bn , ’ fn ) t e r m [ @ @ d e r i v i n g v i s i t o r s { v a r i e t y = " map " ;
a n c e s t o r s = [ " B i n d i n g F o r m s . map " ] }]
t y p e r a w _ t e r m = ( string , s t r i n g ) t e r m t y p e n o m i n a l _ t e r m = ( A t o m . t , A t o m . t ) t e r m t y p e d e b r u i j n _ t e r m = ( unit , int ) t e r m
He getsmultiple representationsof names.
I At least two are used in any single application. (Parsing. Printing.) He getsvisitorsfor free. The methodvisit_absis used at abstractions.
I iter,map,iter2needed in practice. Focusing onmapin this talk.
provided by the binder maestro
The binder
maestro
An easy job?
Implementingvisit_absis the task of our sophisticated binder maestro.
The key is toextend the environmentwhen entering the scope of a binder.
Easy?
Maybe — yet, the binder maestro:
I does not knowwhat operationis being performed,
I does not knowwhat representation(s)of names are in use,
I therefore does not know the types of names and environments,
I let alonehowto extend the environment.
What he knows iswhereandwith what namesto extend the environment.
An easy job?
Implementingvisit_absis the task of our sophisticated binder maestro.
The key is toextend the environmentwhen entering the scope of a binder.
Easy? Maybe — yet, the binder maestro:
I does not knowwhat operationis being performed,
I does not knowwhat representation(s)of names are in use,
I therefore does not know the types of names and environments,
I let alonehowto extend the environment.
What he knows iswhereandwith what namesto extend the environment.
A convention
The binder maestro agrees on adealwith the operations specialist.
“I tell you when to extend the environment; you do the dirty work.”
The binder maestrocallsa method which the operations specialistprovides:
(* A h o o k t h a t d e f i n e s how to e x t e n d the e n v i r o n m e n t . *) m e t h o d p r i v a t e v i r t u a l e x t e n d : ’ env - > ’ bn1 - > ’ env * ’ bn2 This is a bare-bonesAPIfor describing binding constructs.
Visiting an abstraction
The classBindingForms.mapoffers the methodvisit_abs:
c l a s s v i r t u a l [ ’ s e l f ] map = o b j e c t ( s e l f : ’ s e l f ) (* A v i s i t o r m e t h o d for the t y p e abs . *)
m e t h o d p r i v a t e v i s i t _ a b s : ’ t e r m 1 ’ t e r m 2 . _ - >
( ’ env - > ’ t e r m 1 - > ’ t e r m 2 ) - >
’ env - > ( ’ bn1 , ’ t e r m 1 ) abs - > ( ’ bn2 , ’ t e r m 2 ) abs
= f u n _ visit_ ’ t e r m env ( x1 , t1 ) - >
l e t env , x2 = s e l f # e x t e n d env x1 in l e t t2 = visit_ ’ t e r m env t1 in x2 , t2
(* A h o o k t h a t d e f i n e s how to e x t e n d the e n v i r o n m e n t . *) m e t h o d p r i v a t e v i r t u a l e x t e n d : ’ env - > ’ bn1 - > ’ env * ’ bn2 e n d
This method:
I takes avisitor functionfor terms, anenvironment,
I an abstraction, i.e., apairof a name and a term, and
I returns a pair of atransformed nameand atransformed term.
Visiting an abstraction
The classBindingForms.mapoffers the methodvisit_abs:
c l a s s v i r t u a l [ ’ s e l f ] map = o b j e c t ( s e l f : ’ s e l f ) (* A v i s i t o r m e t h o d for the t y p e abs . *)
m e t h o d p r i v a t e v i s i t _ a b s : ’ t e r m 1 ’ t e r m 2 . _ - >
( ’ env - > ’ t e r m 1 - > ’ t e r m 2 ) - >
’ env - > ( ’ bn1 , ’ t e r m 1 ) abs - > ( ’ bn2 , ’ t e r m 2 ) abs
= f u n _ visit_ ’ t e r m env ( x1 , t1 ) - >
l e t env , x2 = s e l f # e x t e n d env x1 in l e t t2 = visit_ ’ t e r m env t1 in x2 , t2
(* A h o o k t h a t d e f i n e s how to e x t e n d the e n v i r o n m e n t . *) m e t h o d p r i v a t e v i r t u a l e x t e n d : ’ env - > ’ bn1 - > ’ env * ’ bn2 e n d
This method:
I takes avisitor functionfor terms, anenvironment,
I an abstraction, i.e., apairof a name and a term, and
I returns a pair of atransformed nameand atransformed term.
Visiting an abstraction
The classBindingForms.mapoffers the methodvisit_abs:
c l a s s v i r t u a l [ ’ s e l f ] map = o b j e c t ( s e l f : ’ s e l f ) (* A v i s i t o r m e t h o d for the t y p e abs . *)
m e t h o d p r i v a t e v i s i t _ a b s : ’ t e r m 1 ’ t e r m 2 . _ - >
( ’ env - > ’ t e r m 1 - > ’ t e r m 2 ) - >
’ env - > ( ’ bn1 , ’ t e r m 1 ) abs - > ( ’ bn2 , ’ t e r m 2 ) abs
= f u n _ visit_ ’ t e r m env ( x1 , t1 ) - >
l e t env , x2 = s e l f # e x t e n d env x1 in l e t t2 = visit_ ’ t e r m env t1 in x2 , t2
(* A h o o k t h a t d e f i n e s how to e x t e n d the e n v i r o n m e n t . *) m e t h o d p r i v a t e v i r t u a l e x t e n d : ’ env - > ’ bn1 - > ’ env * ’ bn2 e n d
This method:
I takes avisitor functionfor terms, anenvironment,
I an abstraction, i.e., apairof a name and a term, and
I returns a pair of atransformed nameand atransformed term.
Visiting an abstraction
The classBindingForms.mapoffers the methodvisit_abs:
c l a s s v i r t u a l [ ’ s e l f ] map = o b j e c t ( s e l f : ’ s e l f ) (* A v i s i t o r m e t h o d for the t y p e abs . *)
m e t h o d p r i v a t e v i s i t _ a b s : ’ t e r m 1 ’ t e r m 2 . _ - >
( ’ env - > ’ t e r m 1 - > ’ t e r m 2 ) - >
’ env - > ( ’ bn1 , ’ t e r m 1 ) abs - > ( ’ bn2 , ’ t e r m 2 ) abs
= f u n _ visit_ ’ t e r m env ( x1 , t1 ) - >
l e t env , x2 = s e l f # e x t e n d env x1 in l e t t2 = visit_ ’ t e r m env t1 in x2 , t2
(* A h o o k t h a t d e f i n e s how to e x t e n d the e n v i r o n m e n t . *) m e t h o d p r i v a t e v i r t u a l e x t e n d : ’ env - > ’ bn1 - > ’ env * ’ bn2 e n d
This method:
I takes avisitor functionfor terms, anenvironment,
I an abstraction, i.e., apairof a name and a term, and
I returns a pair of atransformed nameand atransformed term.
Visiting an abstraction
The classBindingForms.mapoffers the methodvisit_abs:
c l a s s v i r t u a l [ ’ s e l f ] map = o b j e c t ( s e l f : ’ s e l f ) (* A v i s i t o r m e t h o d for the t y p e abs . *)
m e t h o d p r i v a t e v i s i t _ a b s : ’ t e r m 1 ’ t e r m 2 . _ - >
( ’ env - > ’ t e r m 1 - > ’ t e r m 2 ) - >
’ env - > ( ’ bn1 , ’ t e r m 1 ) abs - > ( ’ bn2 , ’ t e r m 2 ) abs
= f u n _ visit_ ’ t e r m env ( x1 , t1 ) - >
l e t env , x2 = s e l f # e x t e n d env x1 in l e t t2 = visit_ ’ t e r m env t1 in x2 , t2
(* A h o o k t h a t d e f i n e s how to e x t e n d the e n v i r o n m e n t . *) m e t h o d p r i v a t e v i r t u a l e x t e n d : ’ env - > ’ bn1 - > ’ env * ’ bn2 e n d
This method:
I takes avisitor functionfor terms, anenvironment,
I an abstraction, i.e., apairof a name and a term, and
I returns a pair of atransformed nameand atransformed term.
Visiting an abstraction
That’sall there isto single-name abstractions.
More binding constructs later on...
For now, let’s turn to the final participant.
The operations
specialist
A toolbox of operations
There are many operations on terms that the end user might wish for:
I testing terms forequalityup toα-equivalence,
I finding out which names arefreeorboundin a term,
I applying arenamingor asubstitutionto a term,
I convertinga term from one representation to another,
I (plus application-specific operations.)
Implementing an operation
To implement one operation, the specialist decides:
I thetypesof names and environments,
I what to doat afree nameoccurrence,
I how to extendthe environment when entering the scope of abound name.
Implementing import
As an example, let’s implementimport, which converts raw terms to nominal terms.
1. An import environment maps strings to atoms:
m o d u l e S t r i n g M a p = Map . M a k e ( S t r i n g ) t y p e env = A t o m . t S t r i n g M a p . t l e t e m p t y : env = S t r i n g M a p . e m p t y
Implementing import
2. When the scope ofxis entered,
the environment is extended with a mapping of the stringxto a fresh atoma.
l e t e x t e n d ( env : env ) ( x : s t r i n g ) : env * A t o m . t = l e t a = A t o m . f r e s h x in
l e t env = S t r i n g M a p . add x a env in env , a
(Anatomcarries a unique integer identity.)
This is true regardless of which binding constructs are traversed.
Implementing import
3. When an occurrence of the stringxis found,
the environment is looked up so as to find the corresponding atom.
e x c e p t i o n U n b o u n d of s t r i n g
l e t l o o k u p ( env : env ) ( x : s t r i n g ) : A t o m . t = t r y S t r i n g M a p . f i n d x env
w i t h N o t _ f o u n d - > r a i s e ( U n b o u n d x )
Implementing import
The previous instructions are grouped in a little class — a “kit”:
c l a s s [ ’ s e l f ] map = o b j e c t ( _ : ’ s e l f ) m e t h o d p r i v a t e e x t e n d = e x t e n d m e t h o d p r i v a t e visit_ ’ fn = l o o k u p e n d
This isKitImport.map.
That’s all there is to it...but...
The end user mustwork
a little bit toglueeverything together...
... and may feelslightly annoyed.
The end user mustwork
a little bit toglueeverything together...
... and may feelslightly annoyed.
Typical glue
For one operation, the end user must write 5 lines of glue code:
l e t i m p o r t _ t e r m env t = (o b j e c t
i n h e r i t [ _ ] map (* g e n e r a t e d by v i s i t o r s *) i n h e r i t [ _ ] K i t I m p o r t . map (* p r o v i d e d by A l p h a L i b *) e n d) # v i s i t _ t e r m env t
For 15 operations, this hurts.
Functorscan help in simple cases, but are not flexible enough.
Macroshelp, but are ugly. Is there a better way?
Towards advanced
binding constructs
Defining new binding constructs
There aremany binding constructsout there.
I “let”, “let rec”, patterns, telescopes, ...
We have seen how toprogrammaticallydefine a binding construct.
Can it be done in a moredeclarativemanner?
A domain-specific language
Here is a little language ofbinding combinators:
t ::= . . . sums, products, free occurrences of names, etc.
| abstraction(p) a pattern, with embedded subterms
| bind(p,t) — sugar for abstraction(p×inner(t))
p ::= . . . sums, products, etc.
| binder(x) a binding occurrence of a name
| outer(t) an embedded term
| rebind(p) a pattern in the scope of any bound names on the left
| inner(t) — sugar for rebind(outer(t))
Inspired by Cαml (F.P., 2005) and Unbound (Weirich et al., 2011).
A domain-specific language
Here is a little language ofbinding combinators:
t ::= . . . sums, products, free occurrences of names, etc.
| abstraction(p) a pattern, with embedded subterms
| bind(p,t) — sugar for abstraction(p×inner(t))
p ::= . . . sums, products, etc.
| binder(x) a binding occurrence of a name
| outer(t) an embedded term
| rebind(p) a pattern in the scope of any bound names on the left
| inner(t) — sugar for rebind(outer(t))
Inspired by Cαml (F.P., 2005) and Unbound (Weirich et al., 2011).
A domain-specific language
Here is a little language ofbinding combinators:
t ::= . . . sums, products, free occurrences of names, etc.
| abstraction(p) a pattern, with embedded subterms
| bind(p,t) — sugar for abstraction(p×inner(t))
p ::= . . . sums, products, etc.
| binder(x) a binding occurrence of a name
| outer(t) an embedded term
| rebind(p) a pattern in the scope of any bound names on the left
| inner(t) — sugar for rebind(outer(t))
Inspired by Cαml (F.P., 2005) and Unbound (Weirich et al., 2011).
Implementation
These primitive constructs are just annotations:
t y p e ’ p a b s t r a c t i o n = ’ p t y p e ’ bn b i n d e r = ’ bn t y p e ’ t o u t e r = ’ t t y p e ’ p r e b i n d = ’ p
Their presence triggers calls to appropriate (hand-written)visit_methods.
Implementation
While visiting a pattern, we keep track of:
I theouter environment, which existed outside this pattern;
I thecurrent environment, extended with the bound names encountered so far.
Thus, while visiting a pattern, we use a richer type ofcontexts:
t y p e ’ env c o n t e x t = { o u t e r : ’ env ; c u r r e n t : ’ env ref }
— Not every visitor method need have the same type of environments!
With this in mind, the implementation of thevisit_methods is straightforward...
Implementation
This code takes place in amapvisitor:
c l a s s v i r t u a l [ ’ s e l f ] map = o b j e c t ( s e l f : ’ s e l f )
m e t h o d p r i v a t e v i r t u a l e x t e n d : ’ env - > ’ bn1 - > ’ env * ’ bn2 (* The f o u r v i s i t o r m e t h o d s are i n s e r t e d h e r e ... *)
e n d
1. At the root of an abstraction,a fresh contextis allocated:
m e t h o d p r i v a t e v i s i t _ a b s t r a c t i o n : ’ env ’ p1 ’ p2 . ( ’ env c o n t e x t - > ’ p1 - > ’ p2 ) - >
’ env - > ’ p1 a b s t r a c t i o n - > ’ p2 a b s t r a c t i o n
= f u n v i s i t _ p env p1 - >
v i s i t _ p { o u t e r = env ; c u r r e n t = ref env } p1
Implementation
2. When a bound name is met, thecurrentenvironment isextended:
m e t h o d p r i v a t e v i s i t _ b i n d e r : _ - >
’ env c o n t e x t - > ’ bn1 b i n d e r - > ’ bn2 b i n d e r
= f u n visit_ ’ bn ctx x1 - >
l e t env = !( ctx . c u r r e n t ) in
l e t env , x2 = s e l f # e x t e n d env x1 in ctx . c u r r e n t := env ;
x2
Implementation
3. When a term that isnot in the scopeof the abstraction is found, it is visited in theouterenvironment.
m e t h o d p r i v a t e v i s i t _ o u t e r : ’ env ’ t1 ’ t2 . ( ’ env - > ’ t1 - > ’ t2 ) - >
’ env c o n t e x t - > ’ t1 o u t e r - > ’ t2 o u t e r
= f u n v i s i t _ t ctx t1 - >
v i s i t _ t ctx . o u t e r t1
Implementation
4. When a subpattern markedrebindis found,
thecurrentenvironment is installed as theouterenvironment.
m e t h o d p r i v a t e v i s i t _ r e b i n d : ’ env ’ p1 ’ p2 . ( ’ env c o n t e x t - > ’ p1 - > ’ p2 ) - >
’ env c o n t e x t - > ’ p1 r e b i n d - > ’ p2 r e b i n d
= f u n v i s i t _ p ctx p1 - >
v i s i t _ p { ctx w i t h o u t e r = !( ctx . c u r r e n t ) } p1 This affects the meaning ofouterinsiderebind.
Example use: telescopes
A dependently-typedλ-calculus whoseΠandλforms involve a telescope:
# d e f i n e t e l e ( ’ bn , ’ fn ) t e l e
# d e f i n e t e r m ( ’ bn , ’ fn ) t e r m t y p e t e l e =
| T e l e N i l
| T e l e C o n s of ’ bn b i n d e r * t e r m o u t e r * t e l e r e b i n d a n d t e r m =
| T V a r of ’ fn
| TPi of ( tele , t e r m ) b i n d
| T L a m of ( tele , t e r m ) b i n d
| T A p p of t e r m * t e r m l i s t [ @ @ d e r i v i n g v i s i t o r s {
v a r i e t y = " map " ;
a n c e s t o r s = [ " B i n d i n g C o m b i n a t o r s . map " ] }]
Conclusion
Conclusion
Visitors arepowerful.
Visitor classes arepartial,composabledescriptions of operations.
Visitorscantraverseabstract syntax with binding.
I Syntax, binding forms, operations can beseparatelydescribed.
I Syntax and even binding forms can be described in adeclarativestyle.
I Open-ended, customizable approach.
Limitations:
I Macros are ugly.
I No proofs.
I Some operations may not fit the visitor framework;
I Some binding forms do not easily fit in the low-level framework or in the higher-level DSL, e.g., Unbound’sRec.