Docstoc

Programming Languages _amp; Software Engineering

Document Sample
Programming Languages _amp; Software Engineering Powered By Docstoc
					 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