# Programming Languages _amp; Software Engineering

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

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

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

• 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

– 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