Document Sample

CSE341: Programming Languages Lecture 10 References, Polymorphic Datatypes, the Value Restriction, Type Inference Ben Wood, filling in for Dan Grossman Fall 2011 More idioms • We know the rule for lexical scope and function closures – Now what is it good for A partial but wide-ranging list: • Pass functions with private data to iterators: Done • Combine functions (e.g., composition) • Currying (multi-arg functions and partial application) • Callbacks (e.g., in reactive programming) • Implementing an ADT with a record of functions Fall 2011 CSE341: Programming Languages 2 Combine functions Canonical example is function composition: fun compose (g,h) = fn x => g (h x) •Creates a closure that “remembers” what g and h are bound to but ('a -> prints •Type ('b -> 'c) * ('a -> 'b) -> the REPL 'c) something equivalent •ML standard library provides this as infix operator o •Example (third version best): fun sqrt_of_abs i = Math.sqrt(Real.fromInt(abs i)) fun sqrt_of_abs i = (Math.sqrt o Real.fromInt o abs) i val sqrt_of_abs = Math.sqrt o Real.fromInt o abs Fall 2011 CSE341: Programming Languages 3 Left-to-right or right-to-left val sqrt_of_abs = Math.sqrt o Real.fromInt o abs As in math, function composition is “right to left” – “take absolute value, convert to real, and take square root” – “square root of the conversion to real of absolute value” “Pipelines” of functions are common in functional programming and many programmers prefer left-to-right – Can define our own infix operator – This one is very popular (and predefined) in F# infix |> fun x |> f = f x fun sqrt_of_abs i = i |> abs |> Real.fromInt |> Math.sqrt Fall 2011 CSE341: Programming Languages 4 Another example • “Backup function” fun backup1 (f,g) = fn x => case f x of NONE => g x | SOME y => y • As is often the case with higher-order functions, the types hint at what the function does ('a -> 'b option) * ('a -> 'b) -> 'a -> 'b • More examples later to “curry” and “uncurry” functions Fall 2011 CSE341: Programming Languages 5 Currying and Partial Application • Recall every ML function takes exactly one argument • Previously encoded n arguments via one n-tuple • Another way: Take one argument and return a function that takes another argument and… – Called “currying” after famous logician Haskell Curry • Example, with full and partial application: – Notice relies on lexical scope val sorted3 = fn x => fn y => fn z => z >= y andalso y >= x val true_ans = ((sorted3 7) 9) 11 val is_non_negative = (sorted3 0) 0 Fall 2011 CSE341: Programming Languages 6 Syntactic sugar Currying is much prettier than we have indicated so far in – Can write e1 e2 e3 e4place of ((e1 e2) e3) e4 in place – Can write fun f x y z = e of fun f x = fn y => fn z => e fun sorted3 x y z = z >= y andalso y >= x val true_ans = sorted3 7 9 11 val is_non_negative = sorted3 0 0 Result is a little shorter and prettier than the tupled version: fun sorted3 (x,y,z) = z >= y andalso y >= x val true_ans = sorted3(7,9,11) fun is_non_negative x = sorted3(0,0,x) Fall 2011 CSE341: Programming Languages 7 Return to the fold J In addition to being sufficient multi-argument functions and pretty, currying is useful because partial application is convenient Example: Often use higher-order functions to create other functions fun fold f acc xs = case xs of [] => acc | x::xs’ => fold f (f(acc,x)) xs’ fun sum_ok xs = fold (fn (x,y) => x+y) 0 xs val sum_cool = fold (fn (x,y) => x+y) 0 Fall 2011 CSE341: Programming Languages 8 The library’s way • So the SML standard library is fond of currying iterators – See types for List.map , List.foldl , List.filter , etc. – So calling them as though arguments are tupled won’t work : • Another example is List.exists fun exists predicate xs = case xs of [] => false | x::xs’ => predicate xs orelse exists predicate xs’ val no = exists (fn x => x=7) [4,11,23] val has_seven = exists (fn x => x=7) Fall 2011 CSE341: Programming Languages 9 Another example Currying and partial application can be convenient even without higher-order functions fun zip xs ys = case (xs,ys) of ([],[]) => [] | (x::xs’,y::ys’) => (x,y)::(zip xs’ ys’) | _ => raise Empty fun range i j = if i>j then [] else i :: range (i+1) j val countup = range 1 (* partial application *) fun add_number xs = zip (countup (length xs)) xs Fall 2011 CSE341: Programming Languages 10 More combining functions • What if you want to curry a tupled function or vice-versa? • What if a function’s arguments are in the wrong order for the partial application you want? Naturally, it’s easy to write higher-order wrapper functions – And their types are neat logical formulas fun other_curry1 f = fn x => fn y => f y x fun other_curry2 f x y = f y x fun curry f x y = f (x,y) fun uncurry f (x,y) = f x y Fall 2011 CSE341: Programming Languages 11 Efficiency So which is faster: tupling or currying multiple-arguments? •They are both constant-time operations, so it doesn’t matter in most of your code – “plenty fast” – Don’t program against an implementation until it matters! •For the small (zero?) part where efficiency matters: – It turns out SML NJ compiles tuples more efficiently – But many other functional-language implementations do better with currying (OCaml, F#, Haskell) • So currying is the “normal thing” and programmers read t1 -> 3-argument function -> t2 -> t3 as a t4 Fall 2011 CSE341: Programming Languages 12 Callbacks A common idiom: Library takes functions to apply later, when an event occurs – examples: – When a key is pressed, mouse moves, data arrives – When the program enters some state (e.g., turns in a game) A library may accept multiple callbacks – Different callbacks may need different private data with different types – Fortunately, a function’s type does not include the types of bindings in its environment – (In OOP, objects and private fields are used similarly, e.g., Java Swing’s event-listeners) Fall 2011 CSE341: Programming Languages 13 Mutable state While it’s not absolutely necessary, mutable state is reasonably appropriate here – We really do want the “callbacks registered” and “events that have been delivered” to change due to function calls For the reasons we have discussed, ML variables really are immutable, but there are mutable references (use sparingly) – New types: t ref where tis a type – New expressions: • ref to create a reference with initial contents e e to • e1 := e2 update contents • !eto retrieve contents (not negation) Fall 2011 CSE341: Programming Languages 14 References example val x = ref 42 val y = ref 42 val z = x val _ = x := 43 val w = (!y) + (!z) (* 85 *) (* x + 1 does not type-check) • A variable bound to a reference (e.g., x) is still immutable: it will always refer to the same reference • But the contents of the reference may change via := • And there may be aliases to the reference, which matter a lot • Reference are first-class values • Like a one-field mutable object, so :=and !don’t specify the field Fall 2011 CSE341: Programming Languages 15 Example call-back library Library maintains mutable state for “what callbacks are there” and provides a function for accepting new ones – A real library would support removing them, etc. – In example, callbacks have type int->unit (executed for side- effect) So the entire public library interface would be the function for registering new callbacks: val onKeyEvent : (int -> unit) -> unit Fall 2011 CSE341: Programming Languages 16 Library implementation val cbs : (int -> unit) list ref = ref [] fun onKeyEvent f = cbs := f :: (!cbs) fun onEvent i = let fun loop fs = case fs of [] => () | f::fs’ => (f i; loop fs’) in loop (!cbs) end Fall 2011 CSE341: Programming Languages 17 Clients Can only register an int -> , so if any other data is needed, must unit be in closure’s environment – And if need to “remember” something, need mutable state Examples: val timesPressed = ref 0 val _ = onKeyEvent (fn _ => timesPressed := (!timesPressed) + 1) fun printIfPressed i = onKeyEvent (fn j => if i=j then print ("pressed " ^ Int.toString i) else ()) Fall 2011 CSE341: Programming Languages 18 Implementing an ADT As our last pattern, closures can implement abstract datatypes – Can put multiple functions in a record – They can share the same private data – Private data can be mutable or immutable (latter preferred?) – Feels quite a bit like objects, emphasizing that OOP and functional programming have similarities See lec9.sml for an implementation of immutable integer sets with operations insert, member, and size The actual code is advanced/clever/tricky, but has no new features – Combines lexical scope, datatypes, records, closures, etc. – Client use is not so tricky Fall 2011 CSE341: Programming Languages 19 More about types • Polymorphic datatypes, type constructors • Why do we need the Value Restriction? • Type inference: behind the curtain Fall 2011 CSE341: Programming Languages 20 Polymorphic Datatypes datatype int_list = EmptyList | Cons of int * int_list datatype ‘a non_mt_list = One of ‘a | More of ‘a * (‘a non_mt_list) datatype (‘a,‘b) tree = Leaf of ‘a | Node of ‘b * (‘a,‘b) tree * (‘a,‘b) tree val t1 = Node(“hi”,Leaf 4,Leaf 8) (* (int,string) tree *) val t2 = Node(“hi”,Leaf true,Leaf 8) (* does not typecheck *) Fall 2011 CSE341: Programming Languages 21 Polymorphic Datatypes datatype ‘a list = [] | :: of ‘a * (‘a list) (* if this were valid syntax *) datatype ‘a option = NONE | SOME of ‘a • list, tree, etc. are not types; they are type constructors • int list, (string,real) tree, etc. are types. • Pattern-matching works on all datatypes. Fall 2011 CSE341: Programming Languages 22 The Value Restriction Appears L If you use partial application to create a polymorphic function, it may not work due to the value restriction – Warning about “type vars not generalized” • And won’t let you call the function – This should surprise you; you did nothing wrong J but you still must change your code – See the written lecture summary about how to work around this wart (and ignore the issue until it arises) – The wart is there for good reasons, related to mutation and not breaking the type system Fall 2011 CSE341: Programming Languages 23 Purpose of the Value Restriction val xs = ref [] (* xs : ‘a list ref *) val _ = xs := [“hi”] (* instantiate ‘a with string *) val y = 1 + (hd (!xs)) (* BAD: instantiate ‘a with int *) • A binding is only allowed to be polymorphic if the right-hand side is: – a variable; or – a value (including function definitions, constructors, etc.) • ref [] is not a value, so we can only give it non-polymorphic types such as int list ref or string list ref, but not ‘a list ref. Fall 2011 CSE341: Programming Languages 24 Downside of the Value Restriction val pr_list = List.map (fn x => (x,x)) (* X *) val pr_list : int list -> (int*int) list = List.map (fn x => (x,x)) val pr_list = fn lst => List.map (fn x => (x,x)) lst fun pr_list lst = List.map (fn x => (x,x)) lst • The SML type checker does not know if the ‘a list type uses references internally, so it has to be conservative and assume it could. • In practice, this means we need to be more explicit about partial application of polymorphic functions. Fall 2011 CSE341: Programming Languages 25 Type inference: sum fun sum xs = case xs of [] => 0 | x::xs’ => x + (sum xs’) sum : t1 -> t2 t1 = t5 list xs : t1 t2 = int x : t3 t3 = t5 xs’ : t4 t4 = t5 list t3 = int t1 = t4 Fall 2011 CSE341: Programming Languages 26 Type inference: sum fun sum xs = case xs of [] => 0 | x::xs’ => x + (sum xs’) t2 sum : t1 -> int t1 = t5 list int xs : t1 t2 = int x : t3 int t3 int = t5 xs’ : t4 t1 t4 t1 = t5 list t3 = int t1 = t4 Fall 2011 CSE341: Programming Languages 27 Type inference: sum fun sum xs = case xs of [] => 0 | x::xs’ => x + (sum xs’) -> sum : int list int t1 = t5 list int xs : int list t2 = int x : int t3 int = t5 xs’ : t4 list int t1 t4 = t5 list t3 = int t1 = t4 Fall 2011 CSE341: Programming Languages 28 Type inference: length fun length xs = case xs of [] => 0 | _::xs’ => 1 + (length xs’) length : t1 -> t2 t1 = t4 list xs : t1 t2 = int xs’ : t3 t3 = t4 list t1 = t3 Fall 2011 CSE341: Programming Languages 29 Type inference: length fun length xs = case xs of [] => 0 | _::xs’ => 1 + (length xs’) t2 length : t1 -> int t1 = t4 list xs : t1 t2 = int t3 xs’ : t1 t3 t1 = t4 list t1 = t3 Fall 2011 CSE341: Programming Languages 30 Type inference: length fun length xs = case xs of [] => 0 | _::xs’ => 1 + (length xs’) t4 -> length : ‘a list int t1 = t4 list -> xs : t4 list int t2 = int xs’ : t4 list t3 t1 = t4 list t1 = t3 length works no matter what ‘a is. Fall 2011 CSE341: Programming Languages 31 Type inference: compose fun compose (f,g) = fn x => f (g x) compose : t1 * t2 -> t3 f : t1 g : t2 t3 = t4 -> t5 x : t4 t2 = t4 -> t6 t1 = t6 -> t7 t5 = t7 Fall 2011 CSE341: Programming Languages 32 Type inference: compose fun compose (f,g) = fn x => f (g x) compose : t1 t2 *-> -> t6) (t4 t2 (t4 -> (t6*-> t5) t3 ->t3 t3 -> t5) f : t1 -> t5 t6 g : t4 t2 -> t6 t3 = t4 -> t5 x : t4 -> t5 t2 = t4 -> t6 t1 = t6 -> t7 t5 t5 = t7 Fall 2011 CSE341: Programming Languages 33 Type inference: compose fun compose (f,g) = fn x => f (g x) compose : (‘a -> ) ) ) ‘b * (‘c -> ‘a -> (‘c -> ‘b f : t1 -> t6 t5 g : t4 t2 -> t6 t3 = t4 -> t5 x : t4 -> t5 t2 = t4 -> t6 t1 = t6 -> t7 t5 t5 = t7 ) ) ) compose : (‘b -> ‘c * (‘a -> ‘b -> (‘a -> ‘c Fall 2011 CSE341: Programming Languages 34 Type inference: broken sum fun sum xs = case xs of [] => 0 | x::xs’ => x + (sum x) sum : t1 -> t2 t1 = t5 list xs : t1 t2 = int x : t3 t3 = t5 xs’ : t4 t4 = t5 list t3 = int t1 = t3 Fall 2011 CSE341: Programming Languages 35 Type inference: sum fun sum xs = case xs of [] => 0 | x::xs’ => x + (sum x) t1 t2 sum : int -> int t1 int = t5 list int t1 xs : int t2 = int x : int t1 t1 int = t5 xs’ : t5 list t4 int t4 = t5 list t1 = int t1 = t3 Fall 2011 CSE341: Programming Languages 36 Parting comments on ML type inference • You almost never have to write types in ML (even on parameters), with some minor caveats. • Hindley-Milner type inference algorithm • ML has no subtyping. If it did, the equality constraints we used for inference would be overly restrictive. • Type variables and inference are not tied to each. Some languages have one without the other. – Type variables alone allow convenient code reuse. – Without type variables, we cannot give a type to compose until we see it used. Fall 2011 CSE341: Programming Languages 37

DOCUMENT INFO

Shared By:

Categories:

Tags:

Stats:

views: | 3 |

posted: | 7/15/2013 |

language: | English |

pages: | 37 |

OTHER DOCS BY yurtgc548

How are you planning on using Docstoc?
BUSINESS
PERSONAL

By registering with docstoc.com you agree to our
privacy policy and
terms of service, and to receive content and offer notifications.

Docstoc is the premier online destination to start and grow small businesses. It hosts the best quality and widest selection of professional documents (over 20 million) and resources including expert videos, articles and productivity tools to make every small business better.

Search or Browse for any specific document or resource you need for your business. Or explore our curated resources for Starting a Business, Growing a Business or for Professional Development.

Feel free to Contact Us with any questions you might have.