The Scala Experience Safe Programming Can be Fun!
The Scala Experience Safe Programming Can be Fun!. Martin Odersky EPFL Lausanne, Switzerland. The .NET Context. Visual Studio Debuggers, Profilers etc. .NET Common Language Runtime. C#. XML Libraries. GUI Libraries, etc. Visual Basic. F#. ML. Graphics Libraries. System.I/O
The Scala Experience Safe Programming Can be Fun!
E N D
Presentation Transcript
The Scala ExperienceSafe Programming Can be Fun! Martin Odersky EPFL Lausanne, Switzerland
The .NET Context Visual Studio Debuggers, Profilers etc. .NET Common Language Runtime C# XML Libraries GUI Libraries, etc. Visual Basic F# ML Graphics Libraries System.I/O System.Net etc. Sockets etc. Database Connection Libraries
F# as a Language Common core language Core ML Core ML Modules-as- values, functors “OCaml-Objects” and other extensions Other extensions .NET Nominal Objects + tools + tools F# OCaml
Today • What is Pattern Matching Really? • Active Patterns • Related Issues and Related work
What is Pattern Matching • F# has four primary syntactic categories • Expressions • Declarations • Types • Patterns
Patterns are Everywhere letpat = expr Binding tryexpr with | pat -> expr | pat -> expr Exception Handling fun pat -> expr Function Values Sequence expressions let f pat ...pat = expr { forpatinexpr forpatinexpr letpat = expr ... -> expr } Function binding matchexprwith | pat -> expr | pat -> expr | pat -> expr Match expressions
Patterns are Everywhere { new type with member x.M1(pat,...,pat) = expr ... member x.MN(pat,...,pat) = expr } Object expressions type C(pat,...,pat) = class memberx.M(pat,...,pat) = expr ... memberx.M(pat,...,pat) = expr end Class definitions
What’s in a Pattern (pat, ..., pat) -- tuple pattern [pat, ..., pat] -- list pattern [| pat, ..., pat |] -- array pattern { id= pat, ..., id=pat } -- record pattern Point(pat, pat) -- data pattern pat | pat -- “either” pattern _ -- wild pattern x -- variable binding 36 -- constant pattern "36" -- constant pattern System.DayOfWeek.Monday -- constant pattern :? type -- type test pattern :? typeas id -- type test pattern null -- null test pattern
Patterns in Action Messages in a multi-threaded app /// Compute requests are sent to the threads. type Request = RequestBlockof view * int /// Response results are returned to the event loop type Response = | ResultBlockofint * ColorBlock | SkippedBlock Consume requests let compute (RequestBlock(view,width)) = ... Consume results match response with | ResultBlock (iframe,block) -> ... | SkippedBlock-> ...
Patterns In Action Fetching from fields/properties let fetch obj info = match info with | :? FieldInfoas f ->f.GetValue(obj) | :? PropertyInfoas p ->p.GetValue(obj,null) | _ ->null Exception catching let conflict = try db.SubmitChanges(); false with | :? OptimisticConcurrencyException->true
Patterns In Action let rewrite f expr = matchexprwith | HoleExpr _ | ConstExpr _ | VarExpr _ ->expr | AppExpr(x1,x2) -> let x1 = rewrite f x1 let x2 = rewrite f x2 AppExpr(x1, x2) | LambdaExpr(id, argvs, body) -> LambdaExpr(f id, argvs, rewrite f body) Term structure in a compiler
Redundancy & Incompleteness let f inp = matchinpwith | [x; y] -> x + y | [x] -> x C:\misc\test.ml(6,4): warning: FS0025: Incomplete pattern match. val f : int list -> int
Redundancy & Incompleteness let f inp = matchinpwith | [x] -> 2 * x | [y] -> y | _ -> 0 C:\misc\test.ml(6,4): warning: FS0026: This rule will never be matched val f : int list -> int
Patterns are incredibly useful... • A major part of the enduring appeal of typed functional languages • They are a fundamental programmer’s workhorse in F# code • However...
Patterns are incredibly bad... • Traditionally no pattern matching on abstract types • Hence encourage people to break abstraction boundaries • They are not extensible • Only one view on a “type” is allowed • Hence mostly effective for sophisticated analysis on term structures in internal implementations
But what is pattern matching? • Pattern matching = inferring a view to decompose a value and applying that view • View = take an input, categorize it and return residue data
Related Work • Views • Wadler 1986, Okasaki 1998 • Active discriminators, ad hoc patterns, unapply methods • Erwig 1996 (nb. called “active patterns”, have recycled the name) • Tullsen 2000 • F# 1.1, 2006 • Odersky & Emir 2006 • Jambon 2007 • Peyton-Jones proposal for Haskell, 2007 • ...
Active Patterns in F# These tags are “active recognizer labels” The whole function is an “active recognizer”. Two views on complex numbers let (|Rect|) (x:complex) = (x.Real, x.Imaginary) let (|Polar|) (x:complex) = (x.Magnitude , x.Phase) They are just ordinary functions with “banana names” letmulViaRect c1 c2 = match c1,c2 with | Rect(ar,ai), Rect(br,bi) -> CreateRect(ar*br - ai*bi, ai*br + bi*ar) letmulViaPolar c1 c2 = match c1,c2 with | Polar(r1,th1),Polar(r2,th2) -> CreatePolar(r1*r2, th1+th2) letmulViaRect (Rect(ar,ai)) (Rect(br,bi)) = CreateRect(ar*br - ai*bi, ai*br + bi*ar) letmulViaPolar (Polar(r1,th1)) (Polar(r2,th2)) = CreatePolar(r1*r2, th1+th2) The use of active recognizer labels implicitly select and apply the function (|Rect|) (|Polar|)
Complete Discriminations let (|Named|Array|ByRef|Ptr|Param|) (typ : System.Type) = iftyp.IsGenericTypethen Named(typ.GetGenericTypeDefinition(), typ.GetGenericArguments()) elif not typ.HasElementTypethen Named(typ, [| |]) eliftyp.IsArraythen Array(typ.GetElementType(), typ.GetArrayRank()) eliftyp.IsByRefthenByRef(typ.GetElementType()) eliftyp.IsPointerthenPtr(typ.GetElementType()) eliftyp.IsGenericParameterthenParam(typ.GenericParameterPosition, typ.GetGenericParameterConstraints()) elsefailwith"MSDN says this can't happen" type System.Type≈ | Named of System.Type * System.Type[] | Array of System.Type * int // rank | ByRef of System.Type | Ptr of System.Type | GenericParam of int * System.Type[] // constraints Multiple tags = View = Total Recognizer Tags can be used on RHS Much simpler and shorter than the if/then/else code letrectoStringtyp = matchtypwith | Named (con, args) ->"(" + con.Name + " ...)" | Array (arg, rank) ->"(Array " + " " + toStringarg + ")" | ByRefarg->"(ByRef " + toStringarg + ")" | Ptrarg->"(Ptr " + toStringarg + ")" | Param(pos,cxs) ->"Param" Note we maintain completeness and redundancy checking
Complete Discriminations (|Named|Array|ByRef|Ptr|Param|)
Partial Recognizers • Total recognizers never fail (except through exceptions and incomplete matches) • Partial recognizers intentionally leave failing cases Option values used to indicate success/failure let (|MulThree|_|) inp = if inp % 3 = 0 then Some(inp/3) else None let (|MulSeven|_|) inp = if inp % 7 = 0 then Some(inp/7) else None match 28 with | MulThree(residue) ->printf"residue = %d!\n" residue | MulSeven(residue) ->printf"residue = %d!\n" residue | _ ->printf"no match!\n"
Partial Recognizers • Partial Recognizers are most useful on “heterogeneous” or “very general” types • Strings • XML • Term structures • Abstracting adhoc queries on other types
Parameterized Partial Recognizers • It’s very useful if partial recognizers can take parameters • “Split a string at character N” • “Match any attribute A on an XmlNode” • “Match any LINQ Expression Tree involving a call to method M” let (|MulN|_|) n inp = if inp % n = 0 then Some(inp/n) else None match 28 with | MulN 3 residue ->printf"residue = %d!\n" residue | MulN 7 residue ->printf"residue = %d!\n" residue | _ ->printf"no match!\n"
Example: XML matching typeGlyphInfo = { bitmapID : int originX : int originY : int width : int height : int } <?xmlversion="1.0" encoding="utf-8" ?> <fontbase="20" height="26"> <bitmaps> <bitmapid="0" name="comic-0.png" size="256x256" /> </bitmaps> <glyphs> <glyphch=" " code="0020" bm="0" origin="0,0" size="1x27" aw="5" lsb="0" /> </glyphs> </font> let (|GlyphElem|_|) inp = matchinpwith | Elem "glyph" (Attributes (Attr"ch" (Char ch) & Attr"code" (NumHex code) & Attr"bm" (Num bm) & Attr"origin" (Pair (Num ox,Numoy)) & Attr"size" (PairX (Num sw,Numsh)) & Attr"aw" (Num aw) & Attr"lsb" (Num lsb))) -> Some {bitmapID = bm; originX = ox; originY = oy; width = sw; height = sh; advanceWidth = aw; leftSideBearing = lsb}
“Both” patterns (pat, ..., pat) -- tuple pattern [pat, ..., pat] -- list pattern [| pat, ..., pat |] -- array pattern { id= pat, ..., id=pat } -- array pattern Point(pat, pat) -- data pattern pat | pat -- “either” pattern pat & pat -- “both” pattern _ -- wild pattern x -- variable binding 36 -- constant pattern "36" -- constant pattern System.DayOfWeek.Monday -- constant pattern :? type -- type test pattern :? typeas id -- type test pattern null -- null test pattern
Example: XML matching // nb using a -?> b == (a -> b option) val ( |Child|_| ) : string -> #XmlNode -?> XmlElement val ( |Elem|_| ) : string -> #XmlNode -?> XmlNode val ( |Attr|_| ) : string ->XmlAttributeCollection -?> string val ( |Num|_| ) : string -?> int32 val ( |Float|_| ) : string -?> float val ( |NumHex|_| ) : string -?> int val ( |Char|_| ) : string -?> char val ( |Pair|_| ) : string -?> (int32 * int32) val ( |PairX|_| ) : string -?> (int32 * int32) valSelectChildren : (XmlNode -?> 'a) -> #XmlNode-> 'a list val ( |Attributes| ) : #XmlNode->XmlAttributeCollection val ( |ChildNodes| ) : #XmlNode->XmlNodeList
Example: Term Structures • Given type Expr = | ConstExpr of ExprConstInfo | VarExpr of ExprVarName | LambdaExpr of ExprVar * Expr | AppExpr of Expr * Expr
Example: Term Structures • Some Typical Active Patterns: let (|App1|_|) = functionAppExpr(ConstExpr(k),x) -> Some(k,x) | _ -> None let (|App2|_|) = functionAppExpr(App1(k,x1),x2) -> Some(k,x1,x2) | _ -> None let (|App3|_|) = functionAppExpr(App2(k,x1,x2),x3) -> Some(k,x1,x2,x3) | _ -> None let (|AppN|_|) = letrecqueryAcc e acc = match e with | AppExpr(f,x) ->queryAcc f (x::acc) | ConstExpr (k) -> Some(k,acc) | _ -> None in fun e ->queryAcc e []
Example: Term Structures • And more: let (|Cond|_|) = function App3(CondOp,e1,e2,e3) -> Some(e1,e2,e3) | _ -> None let (|Tuple|_|) = functionAppN(TupleMkOp(ty),e) -> Some(ty,e) | _ -> None let (|Equality|_|) = function App2(EqualityOp,e1,e2) -> Some(e1,e2) | _ -> None let (|Lambda|_|) = functionLambdaExpr(a,b) -> Some (a,b) | _ -> None let (|App|_|) = functionAppExpr(a,b) -> Some (a,b) | _ -> None let (|Lambdas|) e = qZeroOrMoreRLinear (|Lambda|_|) e let (|Apps|) e = qZeroOrMoreLLinear (|App|_|) e
Example: Term Structures • And more: /// Recognise the compiled form of “a && b” let (|LazyAnd|_|) x = match x with | Cond(Equality(Bool(true),x),y,Bool(false)) -> Some(x,y) | _ -> None let (|LazyOr|_|) x = match x with | Cond(Equality(Bool(true),x),Bool(true),y) -> Some(x,y) | _ -> None let (|BetaReducible|_|) x = match x with | Let((v,e),b) -> Some((v,e),b) | App(Lambda(v,b),e) -> Some((v,e),b) | _ -> None
Other examples • Matching on other term structures • LINQ Expressions (in progress) • Phoenix Compiler Expression Trees (in progress) • Lazy Lists • Mutation, but idempotent • Standard examples from the “views” literature • Join lists, Unzip, etc. etc.
Issues: Syntax/Resolution • Syntax • esp. for parameterized partial patterns • Name resolution • e.g. can you have (|A|B|) and (|A|C|) in scope? • (Yes, but lcan’t mix ‘n match) • Mixing total recognizers • e.g. can you mix A and C from (|A|B|) and (|C|D|)? • (No in the current implementation, but reconsidering this.) • Type checking and inference • Type inference: • Generalization: Recognizers are functions, so Hindley-Milner generalization applies as normal
Issues: Semantics • Naive Semantics • Run each rule separately, on failure go to next rule • Select active recognizers based on single labels • Assume “all active recognizers are idempotent” • Assume all active recognizers give equivalent results on equivalent inputs • Assume no side effects on subsequent executions against equivalent inputs • Hence can optimize • Places a semantic burden on the library designer
Issues: Possible Extensions • Multiple subsets in partial patterns? • Existentials • GADTs • Monadic Generalization let (|A|B|C|_|) inp = ... Reasonable, but NYI – perhaps never will be
Issue: Encoding shows through in types • F# type of total recognizer • Ocaml variants would be useful here: val (|Cons|Nil|) : 'a llist-> Choice<('a * 'a llist),unit> type Choice<'a,'b> = | Choice2_1 of 'a | Choice2_2 of 'b type Choice<'a,'b,'c> = | Choice3_1 of 'a | Choice3_2 of 'b | Choice3_3 of 'c etc. val (|Cons|Nil|) : 'a llist -> [ `Cons of ('a * 'a llist) | `Nil ]
Possible Extensions: Existentials? GADTs? • Existentials are a natural extension to pattern matching in languages with subtyping & generics • But what of active patterns? The natural encoding is to permit anonymous existentials on the right of active recognizers: • But what of GADTs? The natural encoding is to permit anonymous constrainedexistentials on the right of active recognizers: matchobjwith | <'a> :? List<'a> as l -> ... | <'a> :? 'a[] asarr-> ... | <'k,'v> :? Dictionary<'key,'value> -> ... match obj with | <'a> AnyList(l : 'a list) -> ... | <'a> AnyArray(arr : 'a[]) -> ... | <'k,'v> AnyDictionary(dict: Dictionary<'k,'v>) -> ... val (|AnyList|_|) : obj -?> ('a. 'a list) val (|Lambda|_|) : Expr<'a> -?> ('b 'c. ('a = 'b -> 'c) => Var<'b> * Expr<'c>)
Possible Extensions: Monadic Generalization • Generalize types of pattern functions using monads type Pattern<'a,'b> = 'a -> 'b option type Pattern<'M,'a,'b> = 'a -> 'M<'b> when M :> MonadPlus
Possible Extensions: Monadic Generalization • MonadPlus – the pattern matching monad trait MonadPlus<M> ~=~ { static member Return : 'a -> M<'a> static member Bind : M<'a> -> ('a -> M<'b>) -> M<'b> static member Zero : M<'a> static member Plus : M<'a> -> M<'a> -> M<'a> } instance MonadPlus<option> // deterministic evaluation instance MonadPlus<list> // backtracking evaluation instance MonadPlus<STM> // transactional evaluation
Possible Extensions: Monadic Generalization • Transactional pattern matching of lock-free data structures let f q = atomically $ match<STM>q with | stmCons (x, stmCons (y, ys)) ->stmQueue (x + y) ys | stmCons (_, xs) ->return xs implicit retry f q = atomically $ do { (x, xs) <- stmCons q; (y, ys) <- stmCons xs; stmQueue (x+y) ys } `orElse` do { (_, xs) <- stmCons q; return xs } desugaring
Implementation • F# uses Ramsay’s Generalized Pattern Match Algorithm with a simplistic Left-to-right Heuristic • Dumb but effective • Modification is straight-forward • Choose “first N relevant” edges, instead of “all” edges • Leave remaining edges unexplored • If any partial patterns are used we revert to rule-by-rule to avoid exponential blow up • Non left-to-right heuristics can still be used for matching on concrete data or if purity & termination is guaranteed
Performance • Recognizers are functions: many optimizations apply immediately • Currently an allocation on each non-total active pattern invocation • Using .NET structs for Choice and Option would mostly solve this • And no, we don’t yet use these in the F# compiler (which x-compiles with OCaml) • But they feel adequate for our target purposes
Summary • Pattern matching is simultaneous discrimination and decomposition • Active Patterns are in F# 1.9 (heading towards an “F# 2.0” in ~August) • It feels like we’ve reached a fairly stable design point • The feature has many good uses, but new active recognizers are best designed by experienced and trained programmers