260 likes | 411 Views
Good Advice for Type-directed Programming. Aspect-oriented Programming and Extensible Generic Functions. Geoffrey Washburn [ geoffw@cis.upenn.edu ] Joint work with Stephanie Weirich [ sweirich@cis.upenn.edu ]. Introduction. Type-directed programming is a form of generic programming.
E N D
Good Advice for Type-directed Programming Aspect-oriented Programming and Extensible Generic Functions Geoffrey Washburn [geoffw@cis.upenn.edu] Joint work with Stephanie Weirich [sweirich@cis.upenn.edu]
Introduction • Type-directed programming is a form of generic programming. • operation determined by the “shape” of data • many applications (serialization, iteration, etc…) • improves reusability • Key idea: AOP is compelling mechanism for specializing type-directed functions.
Outline • Aspect-oriented programming in AspectML. • Type-directed programming in AspectML. • Specializing type-directed operations via advice. • Comparison of generic extension via aspects with “Scrap Your Boilerplate With Class”.
AOP in AspectML • Aspects cut across the boundaries of other sorts of abstractions. • Aspects are coherent collections of advice. • Advice specifies when and where to perform a computation.
Example advice val f : Int → Bool val g : Int → Int (* trace the argument to f & g *) advice before (|f,g|) (in:Int,stk,info): Int = print ((getFunName info) ^ “ ” ^ (intToString in)); in advice also gets the current call stack and function metadata. the argument that was passed to fand g “when” the advice applies: before f and g are called advice is a declaration form. “where” the advice applies: functions f and g
Advice times in advice before (|f|) (in:a, stk, info) : a = … around proceed before f:a→b after advice around (|f|) (in:a, stk, info) : b = … proceed … out advice after (|f|) (out:b, stk, info) : b = …
Advice in AspectML • “where” is specified by a pointcut. • sets of in-scope function identifiers, (|f1,…,fn|) • the any pointcut • “when” is specified by a time: before, after, or around. • Syntax of advice: advicetimeexpression(x,y,z)=expression
AspectML is dynamic • advice installed during program execution • fun init_module () = let advice before … advice after … in … end • advice has global scope • installation order determines execution order • pointcuts are first-class values • let val x = (| f , g |) advice before x …
Polymorphic advice • Return type could either be Bool or Int. val f : Int → Bool val g : Int → Int (* trace the return value of f & g *) advice after (|f,g|) (out:???,stk,info):??? = print ((getFunName info) ^ “ ” ^ (???)); in
Polymorphic pointcuts • Solution: polymorphic pointcuts and advice • In general pointcuts have a type pc (<a1 … an>σ1→σ2). • σ1 corresponds to argument type of before and around advice. • σ2 corresponds to argument type of after advice. • Type of (|f,g|) is pc (<a> Int→a). • <a> Int→a is least common instance of Int→Bool and Int→Int.
Type analysis (* trace the return value of f & g *) advice after (|f,g|)<a>(out:a,stk,info):a = print ((getFunName info) ^ “ ” ^ (???)); in (* trace the return value of f & g *) advice after (|f,g|)<a>(out:a,stk,info):a = print ((getFunName info) ^ “ ” ^ (typecase a of | Bool ⇒ boolToString out | Int ⇒ intToString out)); in a = Bool a = Int
Generic programming (* trace the return value of f & g *) advice after (|f,g|)<a>(out:a,stk,info):a = print ((getFunName info) ^ “ ” ^ (typecase a of | Bool ⇒ boolToString out | Int ⇒ intToString out)); in (* type-directed serialization *) fun toString <a>(x : a) : String = typecase a of | Bool ⇒ boolToString x | Int ⇒ intToString x | ???
Data type generic programming • Need generic way to handle arbitrary data constructors. • Adapt spines, as developed by Hinze, Löh, and Oliveira.datatype Spine = | SCons : <a> a → Spine a | SApp : <a b> Spine (a → b) → a → Spine b • Spines are a generic representation of data type structure
Creating spines • AspectML primitive toSpine : <a> a → Option (Spine a). • Spines are recursive in argument, not data type.datatype Foo = Bar : Int → Bool → Char → Foo toSpine (Bar 3 True ‘a’) ⇒ Some (SApp (SApp (SApp (SCons Bar) 3) True) ‘a’) SCons SApp SApp SApp Bar True ‘a’ 3
List spine • Only produces Spine if applied to a data constructor. • toSpine 1 ⇒ None toSpine [1,2] ⇒Some (SApp (SApp (SCons ::) 1) [2]) SCons SApp SApp :: [2] 1
Total type-directed function (* type-directed serialization *) fun toString <a>(x: a) : String = typecase a of | Bool ⇒ boolToString x | Int ⇒ intToString x | (b → c) ⇒ “<fn>” | _ ⇒ (case (toSpine x) | Some x’ ⇒ spineToString x’ | None ⇒ raise Error) and spineToString<a>(x: Spine a) : String = case x of | SCons c ⇒ consToString c | SApp spn arg ⇒ “(“ ^ (spineToString spn) ^ “ ” ^ (toString arg) ^ “)”
Overriding defaults • toString works for all values now. • But toString [1,2] produces “(((::) 1) ((:: 2) Nil)))”. • Would like to override the default printing behavior for lists. • Unrealistic to edit toString every time we want to refine the behavior. • Need some kind of specialization mechanism.
Overriding with advice (* extend toString for lists *) advice around (|toString|)<a>(in:a,stk,info)= typecase a of | [b] ⇒ “[” ^ (concat “,” (map toString in)) ^ “]”) | _ ⇒ proceed in) (* extend toString for lists *) case-advice around (|toString|) (in:[a],stk,info) = “[” ^ (concat “,” (map toString in)) ^ “]”)
Benefits of using advice • Pay as you go: original function does not need to be designed for specialization in advance. • Specialized may occur in separate modules from definition. • Separation of function author from data type author. • Specialization without access to the source code. • Specialization at run-time, by dynamically loaded code.
Related approaches to extensibility • How do aspects compare for generic extension with type-classes, ala “Scrap Your Boilerplate with Class”? • Type-classes traditionally a very static mechanism, while aspects in AspectML are very dynamic. • Trade-off: more aggressive optimization may be possible with type-classes versus dynamic extension.
Scrapping your boilerplate with class • Each type-directed operation defined as type-class.class Show a where show :: a → String • Default type-directed operation is implemented by SYB library using explicit dictionary.instance Data ShowD t ⇒ Show t where show v = showConstr (toConstr v) ++ (concat “ “ (gmapQ showPrxy (showD dict) v)) • Specialized behavior by specifying an instance for a type. instance Show a ⇒ Show [a] where show xs = “[” ++ (concat “,” (map show xs)) ++ “]”
Problems with existentials data Exists = Ex :: forall a. a → Exists • Can’t directly write a specialized caseinstance Show Exists where show (Ex (x :: a)) = “pack “ ++ (show x) • Ill typed because don’t know if a has instance for Show. • Could rewrite data type asdata Exists = Ex :: forall a. Show a ⇒ a → Exists • Requires unsupported compiler extension. • Unrealistic to modify Exists for every type-directed operation.
More comparison • Type-classes an entirely compile-time mechanism, not possible to construct new instances at runtime. • AspectML can install aspects at any time. • Useful when working with mobile or dynamically loaded code that may export new data types.
More comparison • Type-class constraints in SYB can become complicated for the user, over-constrained, non-terminating, or unsolvable in practice. • Type-classes have the advantage of enforcing and describing the domain of type-directed functions.
Summary • Advice can be used to specify when and where a computation should occur. • Aspects are symbiotic with type analysis. • Writing extensible type-directed operations with advice avoids limitations of type-class based SYB.
The future • Using information-flow to reason about the use of type analysis and aspects. • Extending AspectML implementation. • Writing large scale software with these techniques. • Ask me if you would like a demo or snapshot of AspectML.