300 likes | 411 Views
This lecture delves into the fundamentals of staged computation, illustrating concepts through various examples related to code transformers and generators. It highlights the distinction between the two, discussing when to use each technique. Additionally, it presents assignments, including a paper on DSL implementation using staging and monads, and emphasizes the concepts introduced in CSE 510 during Winter 2004. The lecture further includes algorithmic frameworks, particularly focusing on the shortest path algorithm in directed graphs and helper functions essential for implementation.
E N D
Fundamentals of Staged Computation Tim Sheard Oregon Graduate Institute Lecture 3: More Examples CSE 510 Section FSC Winter 2004
Assignments • The paper DSL Implementation Using Staging and Monads is now officially assigned. See the web page to obtain a copy. • A volunteer presenter is needed to present the paper next Monday. • Homework 2, due in one week, is now assigned. See webpage for details • Due in one week, Monday Jan 18 Cse583 Winterl 2002
An Interesting Note • Some functions are code transformers • plus : int -> <int> -> <int> • plus x ycode = < ~(lift x) + ~ycode > • Others are code generators • plus' : int -> <int -> int> • plus' x = <fn y => ~(lift x) + y> • When do we use one? When do we use the other? Are they equivalent? Cse583 Winterl 2002
Transformers to generators • Code transformers can be made code generators with a “trick” fun plus’’ n = <fn y => ~(plus n <y>)>; -| plus’’ 3; val it = <(fn a = 3 %+ a)> : <int -> int> Cse583 Winterl 2002
Another example: two versions fun add3 0 y = y | add3 n y = < 1 + ~(add3 (n-1) y)>; fun add4 0 = <fn y => y> | add4 n = <fn y => 1 + ~(add4 (n-1)) y>; Cse583 Winterl 2002
Exponentially large generated programs fun even x = (x mod 2) = 0; fun filter p [] = <[]> | filter p (x::xs) = <if ~p ~(lift x) then ~(filter p xs) else ~(lift x) :: ~(filter p xs)>; -| filter <even> [3]; val it = <if %even 3 then [] else [3]> : <int list> Note the repeated, related (here identical) , recursive calls Cse583 Winterl 2002
But let the list grow -| filter <even> [1,2,3,4]; val it = <if %even 1 then if %even 2 then if %even 3 then if %even 4 then [] else [4] else 3 :: if %even 4 then [] else [4] else 2 :: if %even 3 then if %even 4 then [] else [4] else 3 :: if %even 4 then [] else [4] else 1 :: if %even 2 then if %even 3 then if %even 4 then [] else [4] else 3 :: if %even 4 then [] else [4] else 2 :: if %even 3 then if %even 4 then [] else [4] else 3 :: if %even 4 then [] else [4]> : <int list> Cse583 Winterl 2002
Use let to share calls fun filter p [] = <[]> | filter p (x::xs) = <let val ys = ~(filter p xs) in if ~p ~(lift x) then ys else ~(lift x) :: ys end>; val ex2 = filter <even> [1,2,3,4]; -| val ex2 = <let val a = [] val b = if %even 4 then a else 4 :: a val c = if %even 3 then b else 3 :: b val d = if %even 2 then c else 2 :: c in if %even 1 then d else 1 :: d end> Cse583 Winterl 2002
Staged Shortest Path • Given a directed weighted graph G=(V,E) • Two nodes a,b in V, • The shortest path algorithm finds the minimum weighted path in G from the source a to the destination b. • Where the weights are given by a weight function W Cse583 Winterl 2002
3 5 2 1 4 Representing Graphs fun g x = case x of 1 => [2] | 2 => [3,4] | 3 => [5] | 4 => [1,2,5] | 5 => [4] | _ => []; Cse583 Winterl 2002
Structure of function • fun shortestPath succ source dest marked weight = . . . • succ is the function associating to a vertex a list of successor vertexes, and encodes the topology of the graph • source is the source vertex; • dest is the destination vertex; • marked indicates which nodes have already been visited by the algorithm; • (weight x y) indicates the weight of the edge (x,y). Cse583 Winterl 2002
Helper functions (* min : int -> int -> int *) fun min x z = if x '<' z then x else z ; (* minimum : int list -> int *) fun minimum (x::xs) = (case xs of [] => x | _ => min x (minimum xs)); (* map : ('b -> 'a) -> 'b list -> 'a list *) fun map f [] = [] | map f (a::b) = (f a)::(map f b); (* mem : int -> int list -> bool *) fun mem x [] = false | mem x (a::b) = if a=x then true else mem x b; (* minus : int list -> int list -> int list *) fun minus [] ys = [] | minus (a::b) ys = if mem a ys then minus b ys else a::(minus b ys); Cse583 Winterl 2002
shortestPath fun shortestPath succ source dest marked weight = if source = dest then 0 else let val marked2 = (source::marked) val explore = (minus (succ source) marked2) fun short x = shortestPath succ x dest marked2 weight val path_weights = map (fn node => (short node) + (weight source node)) explore in case path_weights of [] => infinity | _ => (minimum path_weights) end; Cse583 Winterl 2002
3 5 2 1 4 Example • val ex1 = let fun weight x y = 1 in shortestPath g 3 2 [] weight end; -| val ex1 = 3 : int Cse583 Winterl 2002
Staging shortestPath • The graph is known • The source and destination are known • The weight function is dynamic and won’t be known until later. shortestPath : graph -> int -> int -> int list -> (int -> int -> int) -> int shortestPath2 : graph -> int -> int -> int list -> <(int -> int -> int) -> int> Cse583 Winterl 2002
Stage helper functions • Staging the minimum function liftMinimum : <int> list -> <int> fun liftMinimum x = case x of [] => <infinity> | (c::cs) => <min ~c ~(liftMinimum cs)>; Cse583 Winterl 2002
shortestPath2 fun shortestPath2 succ source dest marked = <fn weight => ~(if source = dest then <0> else let val marked2 = source::marked val explore = (minus (succ source) marked) fun short x = shortestPath2 succ x dest marked2 val path_weights = map (fn node => < (~(short node) weight) + (weight ~(lift source) ~(lift node)) > ) explore in (liftMinimum path_weights) end ) >; Note shortestPath2 is in generator style Cse583 Winterl 2002
3 5 2 1 4 Results -| val ex2 = (shortestPath2 g 4 2 []); val ex2 = <(fn a => %min (%min (0 %+ a 1 2) (%infinity %+ a 4 1)) (%min (0 %+ a 4 2) (%min (%infinity %+ a 4 5) %infinity)))> : <(int -> int -> int) -> int> Cse583 Winterl 2002
Unnest the calls to min fun liftMinimum x = case x of [] => <infinity> | [x] => x | ( <infinity> :: cs) => liftMinimum cs | (c::cs) => <let val y = ~(liftMinimum cs) val z = ~c in min z y end>; Note special case for singleton list Cse583 Winterl 2002
shortestPath2 again fun shortestPath2 succ source dest marked = <fn weight => ~(if source = dest then <0> else let val marked2 = source::marked val explore = (minus (succ source) marked) fun short x = shortestPath2 succ x dest marked2 val path_weights = map (fn node => let val recall = < ~(short node) weight> val wght = <weight ~(lift source) ~(lift node)> in <let val q = ~recall in q + ~wght end> end) explore in (liftMinimum path_weights) end ) >; Note the generated let Cse583 Winterl 2002
3 5 2 1 4 Results val ex2 = (shortestPath2 g 4 2 []); -| val ex2 = <(fn a => let val b = %infinity %+ a 4 5 val c = 0 %+ a 4 2 val d = %min c b val e = 0 %+ a 1 2 val f = e %+ a 4 1 in %min f d end)> : <(int -> int -> int) -> int> Cse583 Winterl 2002
Extended example 2 • Staged merge function for sorted lists • Un-staged version fun merge xs ys = case xs of [] => ys | (z::zs) => case ys of [] => xs | w::ws => if z '<' (w:int) then z::(merge zs ys) else w::(merge xs ws); Suppose xs is static and ys is dynamic Cse583 Winterl 2002
First staged attempt fun merge2 xs = <fn ys => ~(case xs of [] => <ys> | (z::zs) => <case ys of [] => ~(lift xs) | (w::ws) => if z '<' (w:int) then z::(~(merge2 zs) ys) else w::(~(merge2 xs) ws) >)>; Goes into an infinite loop. Why? Cse583 Winterl 2002
Second Attempt • First some helper functions fun reverse [] ys = ys | reverse (x::xs) ys = (reverse xs (x::ys)); fun split2 (n:int) xs (small,big) = case xs of [] => (reverse small [], reverse big []) | (z::zs) => if z '>' n then split2 n zs (small,z::big) else split2 n zs (z::small,big); fun split n l = (split2 n l ([],[])); Cse583 Winterl 2002
Unstaged version using the helper functions fun merge3 xs ys = case xs of [] => ys | z::zs => (case ys of [] => xs | w::ws => if z '<' w then z::(merge3 zs ys) else let val (u,v) = split z ys in append u (z::(merge3 zs v)) end ); u is all elements of ys less than z split avoids recursive call on xs Cse583 Winterl 2002
Staged Version fun merge4 xs = <fn ys => ~(case xs of [] => <ys> | (b::bs) => <case ys of [] => ~(lift xs) | (w::ws) => if (~(lift b)) '<' w then (~(lift b))::(~(merge4 bs) ys) else let val (low,high) = split (~(lift b)) ys in append low (~(lift b)::(~(merge4 bs) high)) end >) >; Cse583 Winterl 2002
Not so-good, Generated code -| merge4 [2,5]; val it = <(fn a => (case a of [] => [2,5] | (c::b) => if 2 %'<' c then 2 :: (case a of [] => [5] | (k::j) => if 5 %'<' k then 5 :: a else let val (m,l) = %split 5 a in %append m (5 :: l) end) else let val (e,d) = %split 2 a in %append e (2 :: (case d of [] => [5] | (g::f) => if 5 %'<' g then 5 :: d else let val (i,h) = %split 5 d in %append i (5 :: h) end)) end))> : <int list -> int list> Cse583 Winterl 2002
Improve by • use transformer style • use let to share recursive call fun merge5 xs ys = case xs of [] => ys | (b::bs) => <case ~ys of [] => ~(lift xs) | (w::ws) => let val tail = ~(merge5 bs ys) val (low,high) = split (~(lift b)) ~ys in if (~(lift b)) '<' w then (~(lift b)):: tail else append low (~(lift b):: tail) end >; fun f1 xs = <fn ys => ~(merge5 xs <ys>)>; Cse583 Winterl 2002
Better code -| f1 [2,3]; val it = <(fn a => (case a of [] => [2,3] | (c::b) => let val d = case a of [] => [3] | (f::e) => let val (h,g) = %split 3 a in if 3 %'<' f then 3 :: a else %append h (3 :: a) end) val (j,i) = %split 2 a in if 2 %'<' c then 2 :: d else %append j (2 :: d) end))> Cse583 Winterl 2002
Review • Multiple stage programs require nested brackets. Transformer style is often useful. • Staged helper functions are often necessary. • Generated programs must follow all paths of a generated if or case, even if the unstaged version will follow only one path. • Leads to possiblility of exponentially growing code. • Can cause the generator not to terminate if recursive call at first level doesn’t “get smaller”, even though it gets smaller (in the second stage) in the unstaged program. • Use let to share similar or related code to avoid this blowup. • Use let to avoid deeply nested function calls. Simplifies the structure, and won’t blow up the object level compiler by generating code in weird ways. Cse583 Winterl 2002