310 likes | 464 Views
Implementing Staged Computation. Chiyan Chen and Hongwei Xi Boston University. Talk Overview. Staged Computation Guarded Recursive Datatype Constructors Code Representation Higher-order Abstract Syntax First-order Abstract Syntax (via de Bruijn indices). An Example in Scheme.
E N D
Implementing Staged Computation Chiyan ChenandHongwei Xi Boston University
Talk Overview • Staged Computation • Guarded Recursive Datatype Constructors • Code Representation • Higher-order Abstract Syntax • First-order Abstract Syntax (via de Bruijn indices)
An Example in Scheme (define (run code) (eval code nil)) (define (power n x)(if (= n 0) 1 `(* ,x ,(power (- n 1) x)))) ;;; (power 2 ‘x) yields (* x (* x 1)) (define square(run `(lambda (x) ,(power 2 ‘x)))) ;;; (power 3 ‘y) yields (* y (* y (* y 1))) (define cube(run `(lambda (y) ,(power 3 ‘y))))
An Example in MetaML (* ‘run’ is a built-in function of the type <‘a> -> ‘a *) fun power (n: int) (x: <int>): <int> =if n = 0 then <1>else <~x * ~(power (n-1) x)> val square = run <fn x => ~(power 2 <x>)> val cube = run <fn x => ~(power 3 <x>)>
Scheme vs. MetaML • Most significantly, there is a type system in MetaML but none in Scheme for verifying whether staging is done correctly. • However, ‘code’ can be readily represented in Scheme but not in ML
Scheme vs. MetaML (continued) For instance, (define (power n x)(if (= n 0) 1 `(* ,x ,(power (- n 1) x)))) can really be defined as follows: (define (power n x)(if (= n 0) 1 (list ‘* x (power (- n 1) x))))
G.R. Datatype Constructors • A simple and natural generalization of the notion of datatypes in ML • We use $D.t for a guarded type, where D may contain some constraints on type variables in t • A g.r. datatype constructor is used to construct g.r. datatypes, that is, sums of guarded types.
A Representation for Types The g.r. datatype constructor TY is for representing simple types: typecon (type) TY = (int) TYint| {’a,’b}. (’a * ’b) TYtup of ’a TY * ’b TY| {’a,’b}. (’a-> ’b) TYfun of ’a TY * ’b TY| {’a}. (’a TY) TYtyp of ’a TY TYint: (int) TY TYtup: {’a,’b}. ’a TY * ’b TY -> (’a * ’b) TY TYfun: {’a,’b}. ’a TY * ’b TY -> (’a -> ’b) TY TYtyp: {’a}. ’a TY -> (’a TY) TY
The Representation of Some Types • The type ‘int’ is represented as TYint • The type ‘int * int’ is represented as TYtup(TYint, TYint) • The type ‘int -> int * int’ is represented as TYfun(TYint, TYtup (TYint, TYint)) • The type ‘(int) TY’ is represented as TYtyp(TYint)
A Program Example: val2string fun val2string pf x =case pf of TYint => int2string x| TYtup (pf1, pf2) => “(” ^ val2string pf1 (fst x) ^ “,” ^ val2string pf2 (snd x) ^ “)”| TYfun _ => “[a function]”| TYtyp _ => “[a type]” withtype {’a}. ’a TY -> ’a -> string
H.O.A.S. Trees typecon (type) HOAS = {’a}. (’a) HOASlift of ’a| {’a}. (’a) HOASif of bool HOAS * ’a HOAS * ’a HOAS| {’a,’b}. (’a * ’b) HOAStup of ’a HOAS * ’b HOAS| {’a,’b}. (’a -> ’b) HOASlam of ’a HOAS -> ’b HOAS| {‘a,’b}. (‘b) HOASapp of (‘a ->’b) HOAS * ‘a HOAS| {’a}. (’a) HOASfix of ’a HOAS -> ’a HOAS HOASlift: {’a}. ’a -> ’a HOAS HOASif: {’a}. bool HOAS * ’a HOAS * ’a HOAS -> ’a HOAS HOAStup: {’a,’b}. ’a HOAS * ’b HOAS -> (’a * ’b) HOAS HOASlam: {’a,’b}. (’a HOAS -> ’b HOAS) -> (’a -> ’b) HOAS HOASapp: {‘a,’b}. (‘a -> ‘b) HOAS * ‘a HOAS -> ‘b HOAS HOASfix: {’a}. (’a HOAS -> ’a HOAS) -> ’a HOAS
A Type-Preserving Evaluator fun eval (HOASlift v) = v| eval (HOASif (b, e1, e2)) = if eval (b) then eval (e1) else eval (e2)| eval (HOAStup (e1, e2)) = (eval (e1), eval (e2))| eval (HOASlam (f)) = fn x => eval (f (HOASlift x))| eval (HOASapp (e1,e2)) = (eval e1) (eval e2)| eval (HOASfix f) = eval (f (HOASfix f)) withtype {’a}. ’a HOAS -> ’a
Compiling H.O.A.S. Trees • We need a compilation function that can compile h.o.a.s trees: run: {’a}. ’a HOAS -> ’a (think about normalization by evaluation) • For instance, this can be done in such a manner …
A Staged Program • fun power1 n = HOASlam ( fn x: int HOAS => if n = 0 then HOASlift 1 else HOASapp ( HOASlift op *, HOAStup (x, HOASapp (power1 n-1, x)))) withtype int -> (int -> int) HOAS • val square1 = run (power1 2) • The function square1 is like being defined as: fun square1 x = x * f1 (x) and f1(x) = x * f0(x) and f0(x) = 1
Another Staged Program • fun power2 n x = if n = 0 then HOASlift 1 else HOASapp (HOASlift op*, HOAStup (x, power2 (n-1) x)withtype int -> int HOAS -> int HOAS • val square2 = run (HOASlam (power2 2)) • The function square2 is like being defined as fun square2 x = x * (x * 1)
Syntactic Sugar • <t> = (t) HOAS • `(x) = HOASlift (x) • `(c) = HOASlift (c) • `(e1, e2) = HOAStup (`(e1), `(e2)) • `(e1(e2)) = HOASapp(`(e1), `(e2)) • `(fn x => e) = HOASlam (fn x => `(e[x -> ^x])) • `(fix f => e) = HOASfix (fn f => `(e[f -> ^f])) • `(e: t) = (`(e): (t) HOAS) • `(^(e)) = e
An Example in Sugared Syntax • fun power1 n = `(fn x => ^(if n = 0 then lift (1) else `(x * ^(power1 (n-1)) x))) withtype int -> <int -> int> • val square1: int -> int = run (power1 2)
Another Example in Sugared Syntax • fun power2 n x = if n = 0 then lift (1) else `(^x * ^(power2 (n-1) x))withtype int -> <int> -> <int> • val square2: int -> int = run `(fn x => ^(power2 2 `(x))
The Usual Ackermann Function fun acker m n =if m =0 then n+1else acker (m-1) (if n = 0 then 1 else acker m (n-1)) withtype int ->int -> int
A Staged Ackermann Function • The Ackermann function can be staged as follows: fun acker m = `(fix f => fn n => ^(if m = 0 then `(n+1) else `(^(acker (m-1)) (if n = 0 then 1 else f (n-1))))) withtype int -><int -> int>
A Staged Ackermann Function (contd.) • For instance, we can define a function acker2 as follows: fun acker2 = run (acker 2)This definition is similar to the following one:fun acker2 n = acker1 (if n = 0 then 1 else acker2 (n-1))and acker1 n = acker0 (if n = 0 then 1 else acker1 (n-1))and acker0 n = n+1
A Problem with H.O.A.S. Trees • The expression e = `(fn x => ^(run `(x))), whose translation is HOASlam (fn x => run x), can be assigned the type: {‘a}. (‘a HOAS -> ‘a) HOAS • However, ‘run e’ causes a run-time error
F.O.A.S. Trees typecon (type,type) FOAS =| {’a,’g}. (’a,’g) FOASlift of ’a| {’a,’g}. (‘a,’a*’g) FOASone| {’a,’b,’g}. (‘a,’b*’g) FOASshift of (‘a,’g) FOAS| {‘a,’b,’g}. (‘a -> ‘b,’g) FOASlam of (‘b,’a*’g) FOAS| … FOASlift: {’a,’g}. ’a -> (’a,’g) FOAS FOASone: {‘a,’g}. (‘a,’a*’g) FOAS FOASshift: {‘a,’b’,’g}. (‘a,’g) FOAS -> (‘a,’b*’g) FOAS FOASlam: {‘a,’b,’g}. (‘b,’a * ‘g) FOAS -> (‘a -> ‘b,’g) FOAS …
Some Examples of F.O.A.S. Trees • The F.O.A.S. tree for ‘fn x => x’: FOASlam (FOASone) • The F.O.A.S. tree for ‘fn x => fn y => x+y’: FOASlam(FOASlam( FOASapp (FOASlift +, FOAStup (FOASshift (FOASone), FOASone))))
Some Examples of F.O.A.S. Trees • FOASone can be assigned the type:(int, int * unit) FOAS • FOASapp (FOASone, FOASshift(FOASone)) can be assigned the type:(int, (int -> int) * (int * unit)) FOAS • FOASapp (FOASshift(FOASone), FOASone) can be assigned the type:(int, int * ((int -> int) * unit)) FOAS
Compiling F.O.A.S trees • We need a function to compile closed F.O.A.S. trees: run: {‘a}. (‘a, unit) FOAS ->‘a • This can be readily implemented …
A Simple Example fun power1 n =if n = 0 then FOASlam (FOASlift 1)else FOASlam ( FOASapp ( FOASlift *, FOAStup (FOASone, FOASapp (power1 (n-1), FOASone)))) withtype {‘g}. int -> (int -> int, ‘g) FOAS val square = run (power1 2)
Another Simple Example fun power2 n x =if n = 0 then FOASlift (1)else FOASapp (FOASlift *, FOAStup (x, power2 (n-1) x)) withtype{‘g}. int -> (int, ‘g) FOAS -> (int, ‘g) FOAS val square = run (FOASlam (power2 2 FOASone))
Conclusion • We have presented two approaches (h.o.a.s. and f.o.a.s.) to representing ‘code’ through the use of g.r. datatype constructors • With such concrete represention of code, we can implement staged computation by simply translating away staging notations in staged programs
Related Work • Guarded Recursive Datatype Constructors (Xi et al) • MetalML (Sheard et al) • Staged Computation (Pfenning and Davis) • … …
End of the Talk Thank you! Questions?