Presentation is loading. Please wait.

Presentation is loading. Please wait.

Introduction to ML A Quasi-Functional Language With Strong Typing And Type Inference.

Similar presentations


Presentation on theme: "Introduction to ML A Quasi-Functional Language With Strong Typing And Type Inference."— Presentation transcript:

1 Introduction to ML A Quasi-Functional Language With Strong Typing And Type Inference

2 Example of Interactive Session Conventional syntax: Conventional syntax: val x = 5; (*user input *) val x = 5; (*user input *) > val x = 5: int (*system response*) > val x = 5: int (*system response*) fun len lis = if (null lis) then 0 else 1 + len (tl lis); fun len lis = if (null lis) then 0 else 1 + len (tl lis); > val len = fn : ‘a list -> int > val len = fn : ‘a list -> int Type inference for local entities Type inference for local entities x * x * x; x * x * x; > val it = 125: int (* it denotes last computation*) > val it = 125: int (* it denotes last computation*)

3 Operations on lists Similar to LISP, with (hd, tl, ::) instead of (car, cdr, cons) Similar to LISP, with (hd, tl, ::) instead of (car, cdr, cons) - fun append (x, y) = if null (x) then y - fun append (x, y) = if null (x) then y else hd (x) :: append (tl (x), y); else hd (x) :: append (tl (x), y); > val append = fn: ‘a list * ‘a list -> ‘a list > val append = fn: ‘a list * ‘a list -> ‘a list (* a function that takes a pair of lists and yields a list *) (* a function that takes a pair of lists and yields a list *) - fun upto (m, n) = - fun upto (m, n) = if m > n then [ ] else m :: upto (m+1, n); if m > n then [ ] else m :: upto (m+1, n); > val upto = fn : int * int -> int list > val upto = fn : int * int -> int list (* a function that takes two numbers and yields a list *) (* a function that takes two numbers and yields a list *)

4 Patterns A simple mechanism to describe and name parts of a structure fun prod [ ] = 1 (* if list is empty *) A simple mechanism to describe and name parts of a structure fun prod [ ] = 1 (* if list is empty *) | prod (n::ns) = n * prod (ns); (* n is first element *) | prod (n::ns) = n * prod (ns); (* n is first element *) A list can be described by its head and its tail A list can be described by its head and its tail fun maxl [m] : int = m fun maxl [m] : int = m | maxl (m::n::ns) if m > n then maxl (m ::ns) | maxl (m::n::ns) if m > n then maxl (m ::ns) else maxl (n :: ns) else maxl (n :: ns)

5 Recursion and iteration A function that returns the first i elements of a list: A function that returns the first i elements of a list: fun take ([ ], I) = [ ] fun take ([ ], I) = [ ] | take (x::xs, I) = if I > 0 then x::take (xs, I - 1) else [ ]; | take (x::xs, I) = if I > 0 then x::take (xs, I - 1) else [ ]; Non-recursive version: introduce an accumulator argument: Non-recursive version: introduce an accumulator argument: fun itake ([ ], _, taken) = taken (* _ is pattern for anything *) fun itake ([ ], _, taken) = taken (* _ is pattern for anything *) | itake (x::xs, I, taken) = | itake (x::xs, I, taken) = if I > 0 then itake (xs, I - 1, x::taken) if I > 0 then itake (xs, I - 1, x::taken) else taken; else taken; > var rtake = fn: ‘a list * int * ‘a list -> ‘a list > var rtake = fn: ‘a list * int * ‘a list -> ‘a list

6 A classic in functional form: quicksort fun quick [ ] = [ ] fun quick [ ] = [ ] | quick [x:real] = [x] | quick [x:real] = [x] | quick (a::bs) = | quick (a::bs) = let fun partition (left, right, [ ]) = let fun partition (left, right, [ ]) = (quick left) @ (quick right) (quick left) @ (quick right) | partition (left, right, x::xs) = | partition (left, right, x::xs) = if x <= a then partition (x::left, right, xs) if x <= a then partition (x::left, right, xs) else partition (left, x:: right, xs) else partition (left, x:: right, xs) in in partition ([a], [ ], bs) end; partition ([a], [ ], bs) end; > val quick : fn : real list -> real list;

7 A single formal is sufficient If function takes more than one argument, say that it takes a single composite one: If function takes more than one argument, say that it takes a single composite one: fun exp (x, n) = if n = 0 then 1 fun exp (x, n) = if n = 0 then 1 else x * exp (x, n-1); else x * exp (x, n-1); > val exp = fn : int * int -> int > val exp = fn : int * int -> int Single argument is a tuple (int, int) Single argument is a tuple (int, int) Can also define records with named components Can also define records with named components

8 The type system Primitive types: bool, int, char, real, string Primitive types: bool, int, char, real, string Constructors: list, array, product (record), function Constructors: list, array, product (record), function an expression has a corresponding type expression an expression has a corresponding type expression the interpreter builds the type expression for each input the interpreter builds the type expression for each input type checking requires that type expression of functions and their arguments match, and that type expression of context match result of function type checking requires that type expression of functions and their arguments match, and that type expression of context match result of function

9 Boolean Values Two values Two values true and false true and false The usual set of logical operators etc The usual set of logical operators etc

10 Integers (type int) Can notate in either decimal or hex (0xaa) Can notate in either decimal or hex (0xaa) Negative sign uses ~ Negative sign uses ~ This is because unary minus is an operator This is because unary minus is an operator But ~ is part of the number But ~ is part of the number Most languages don’t have negative literals Most languages don’t have negative literals Which is an odd corner Which is an odd corner All the usual arithmetic operators All the usual arithmetic operators

11 Unsigned (type word) A distinct type from int A distinct type from int Notated as 123w or 0wxff Notated as 123w or 0wxff Must be able to tell type of literal Must be able to tell type of literal From the literal itself From the literal itself Without any context Without any context Usual arithmetic operators Usual arithmetic operators (but not abs, unary minus) (but not abs, unary minus)

12 Floating-point Type (real) Notated with either a decimal point and a fractional part or an exponent or both Notated with either a decimal point and a fractional part or an exponent or both 3.45 3.45 3E4 3E4 Usual arithmetic operators Usual arithmetic operators Note that usual arithmetic operators are overloaded, but must be able to tell type! Note that usual arithmetic operators are overloaded, but must be able to tell type! No implicit conversions between numeric types, functions in library used instead. No implicit conversions between numeric types, functions in library used instead.

13 String Type String is a primitive String is a primitive Not a vector of characters Not a vector of characters Usual notation (like Ada or C) Usual notation (like Ada or C) Carret (^) is concatenation Carret (^) is concatenation There is also a type char for single chars There is also a type char for single chars Function str makes char into a string Function str makes char into a string Note everything is case sensitive Note everything is case sensitive All keywords lower case All keywords lower case

14 Example of Simple Arithmetic Zeller function for day of the week Zeller function for day of the week val floor = Real.floor val real = Real.fromInt val zc = fn (d, m, y, c) => (floor (2.61 * real (m) - 0.2) + d + y + y div 4 + c div 4 - 2 * c) mod 7; val floor = Real.floor val real = Real.fromInt val zc = fn (d, m, y, c) => (floor (2.61 * real (m) - 0.2) + d + y + y div 4 + c div 4 - 2 * c) mod 7; Note that the following are equivalent val zc = fn … fun zc … Note that the following are equivalent val zc = fn … fun zc …

15 Local Variables Zeller function rewritten (floor, real local) Zeller function rewritten (floor, real local) local val floor = Real.floor val real = Real.fromInt in val zc = fn (d, m, y, c) => (floor (2.61 * real (m) - 0.2) + d + y + y div 4 + c div 4 - 2 * c) mod 7; end; local val floor = Real.floor val real = Real.fromInt in val zc = fn (d, m, y, c) => (floor (2.61 * real (m) - 0.2) + d + y + y div 4 + c div 4 - 2 * c) mod 7; end;

16 Use of let Local allows decls used in other decls Local allows decls used in other decls Let allows decls used in expressions Let allows decls used in expressions (* Radix for non-negative nums *) val rec radix = fn (n, base) => let val b = size base val digit = fn n => str (String.sub (base, n)) val radix' = fn (true, n) = digit n | (false, n) => radix (n div b, base) ^ digit (n mod b) in radix' (n let val b = size base val digit = fn n => str (String.sub (base, n)) val radix' = fn (true, n) = digit n | (false, n) => radix (n div b, base) ^ digit (n mod b) in radix' (n < b, n) end;

17 Block structure and nesting Example of nesting: Example of nesting: fun findroot (a, x, acc) = fun findroot (a, x, acc) = (* standard Newton-Raphson *) (* standard Newton-Raphson *) let val nextx = (a / x +x) / 2.0 (*next approximation *) let val nextx = (a / x +x) / 2.0 (*next approximation *) in in if abs (x - nextx) < acc*x then nextx if abs (x - nextx) < acc*x then nextx else findroot (a, nextx, acc ) else findroot (a, nextx, acc ) end; end; (*tail recursion rather than iteration *) (*tail recursion rather than iteration *)

18 What is an ML Program So far we have seen functions and expressions, but what is an ML program? So far we have seen functions and expressions, but what is an ML program? Answer, a bunch of definitions and an expression to evaluate, typically: Answer, a bunch of definitions and an expression to evaluate, typically: let declarations in expression end; let declarations in expression end;

19 Functionals fun exists pred [ ] = false fun exists pred [ ] = false | exists pred (x::xs) = (pred x) orelse exists pred xs; | exists pred (x::xs) = (pred x) orelse exists pred xs; (* pred is a predicate : a function that delivers a boolean *) > val exists : fn : (‘a -> bool) -> ‘a list -> bool > val exists : fn : (‘a -> bool) -> ‘a list -> bool fun all pred [ ] = true fun all pred [ ] = true | all pred (x::xs) = (pred x) andalso all pred xs; > val all : fn : (‘a -> bool) -> ‘a list -> bool | all pred (x::xs) = (pred x) andalso all pred xs; > val all : fn : (‘a -> bool) -> ‘a list -> bool fun filter pred [ ] = [ ] fun filter pred [ ] = [ ] | filter (x::xs) = | filter (x::xs) = if pred x then x :: filter pred xs if pred x then x :: filter pred xs else filter pred xs; else filter pred xs; > val filter = fn : (‘a -> bool) -> ‘a list –> ‘a list > val filter = fn : (‘a -> bool) -> ‘a list –> ‘a list

20 Currying: partial bindings a b c means (a b) c: (a b) yields a function that is applied to c a b c means (a b) c: (a b) yields a function that is applied to c fun app2 x y = if null x then y fun app2 x y = if null x then y else (hd x) :: app2 (tl x) y; else (hd x) :: app2 (tl x) y; > val app2 = fn : ‘a list -> ‘a list -> a’list > val app2 = fn : ‘a list -> ‘a list -> a’list (*a function that given a list yields a function that takes a list and yields a list *) (*a function that given a list yields a function that takes a list and yields a list *) val ap123 = app2 [1, 2, 3]; val ap123 = app2 [1, 2, 3]; > val ap123 = fn : int list -> int list > val ap123 = fn : int list -> int list ap123 [10, 20, 30]; ap123 [10, 20, 30]; > val it = [1, 2, 3, 10, 20, 30] > val it = [1, 2, 3, 10, 20, 30]

21 Type inference fun incr x = x + 1; fun incr x = x + 1; > val incr = fn : int -> int > val incr = fn : int -> int because of its appearance in (x+1), x must be integer because of its appearance in (x+1), x must be integer fun add2 x = incr (incr x); fun add2 x = incr (incr x); > val add2 = fn : int -> int > val add2 = fn : int -> int incr returns an integer, so add2 does as well incr returns an integer, so add2 does as well x is argument of incr, so must be integer x is argument of incr, so must be integer val wrong = 10.5 + incr 7; val wrong = 10.5 + incr 7; > Error: operator and operand don’t agree > Error: operator and operand don’t agree

22 Polymorphism fun len x = if null x then 0 fun len x = if null x then 0 else 1 + len (tl x); else 1 + len (tl x); Works for any kind of list. What is its type expression? Works for any kind of list. What is its type expression? > val len : fn = ‘a list -> int > val len : fn = ‘a list -> int ‘a denotes a type variable. Implicit universal quantification: ‘a denotes a type variable. Implicit universal quantification: for any a, len applies to a list of a’s. for any a, len applies to a list of a’s. fun copy lis = if null lis then nil fun copy lis = if null lis then nil else hd (lis) :: copy (tl lis); else hd (lis) :: copy (tl lis);

23 Type inference and unification Type expressions are built by solving a set of equations Type expressions are built by solving a set of equations fun reduce f lis init = if null lis then init fun reduce f lis init = if null lis then init else f ((hd lis), reduce f (tl lis) init)) else f ((hd lis), reduce f (tl lis) init)) null : ‘a list -> bool null : ‘a list -> bool hd : ‘b list -> ‘b hd : ‘b list -> ‘b tl : ‘c list -> ‘c list tl : ‘c list -> ‘c list apply : (‘d -> e’) * ‘d -> ‘e (*function application*) apply : (‘d -> e’) * ‘d -> ‘e (*function application*) let b be the type of init. Let a be the element type of lis. let b be the type of init. Let a be the element type of lis. Then f takes an a and a b and returns a b. Then f takes an a and a b and returns a b. > val reduce = fn : (‘a -> ‘b -> ‘b) -> (‘a list) -> ‘b -> ‘b > val reduce = fn : (‘a -> ‘b -> ‘b) -> (‘a list) -> ‘b -> ‘b

24 Unification algorithm A type variable can be unified with another variable A type variable can be unified with another variable ‘a unifies with ‘b => ‘a and ‘b are the same ‘a unifies with ‘b => ‘a and ‘b are the same A type variable can be unified with a constant A type variable can be unified with a constant ‘a unifies with int => all occurences of ‘a mean int ‘a unifies with int => all occurences of ‘a mean int A type variable can be unified with an expression A type variable can be unified with an expression ‘a unifies with ‘b list ‘a unifies with ‘b list ‘a does not unify with ‘a list ‘a does not unify with ‘a list A constant can be unified with itself int is int A constant can be unified with itself int is int An expression can be unified with another expression if the constructors are identical and if the arguments can be unified (recursive): An expression can be unified with another expression if the constructors are identical and if the arguments can be unified (recursive): (int -> int) list unifies with ‘a list, ‘a is a function on integers (int -> int) list unifies with ‘a list, ‘a is a function on integers

25 Type system does not handle overloading well fun plus x y = x + y; fun plus x y = x + y; operator is overloaded, cannot be resolved from context (error in some versions, defaults to int in others) operator is overloaded, cannot be resolved from context (error in some versions, defaults to int in others) The type system can decide that a function takes ‘a but not that it takes an int or real The type system can decide that a function takes ‘a but not that it takes an int or real Can use explicit typing to select interpretation: Can use explicit typing to select interpretation: fun mix (x, y,z) = x * y + z:real; fun mix (x, y,z) = x * y + z:real; > mix : (real * real * real) -> real > mix : (real * real * real) -> real

26 Parametric polymorphism vs. generics A function whose type expression has type variables applies to an infinite set of types. A function whose type expression has type variables applies to an infinite set of types. Equality of type expressions means structural equivalence. Equality of type expressions means structural equivalence. All applications of a polymorphic function use the same body: no need to instantiate. All applications of a polymorphic function use the same body: no need to instantiate. let val ints = [1, 2, 3]; let val ints = [1, 2, 3]; val strs = [“this”, “that”]; val strs = [“this”, “that”]; in in len ints + len strs (* int list -> int, string list -> int *) len ints + len strs (* int list -> int, string list -> int *) end; end; > val it = 5: int > val it = 5: int

27 User-defined types and inference A user-defined type introduces constructors: A user-defined type introduces constructors: datatype tree = leaf of int | node of tree * tree datatype tree = leaf of int | node of tree * tree leaf and node are type constructors leaf and node are type constructors can define functions by pattern: can define functions by pattern: fun sum (leaf (t)) = t fun sum (leaf (t)) = t | sum (node (t1, t2)) = sum t1 + sum t2; | sum (node (t1, t2)) = sum t1 + sum t2; > val sum = fn : tree -> int > val sum = fn : tree -> int

28 Parameterized datatypes fun flatten (leaf (t)) = [t] fun flatten (leaf (t)) = [t] | flatten (node (t1, t2)) = flatten (t1) @ flatten (t2); | flatten (node (t1, t2)) = flatten (t1) @ flatten (t2); > flatten: tree -> int list > flatten: tree -> int list datatype ‘a gentree = leaf of ‘a datatype ‘a gentree = leaf of ‘a | node of ‘a gentree * ‘a gentree; | node of ‘a gentree * ‘a gentree; val names = node (leaf (“this”), leaf (“that”)); val names = node (leaf (“this”), leaf (“that”)); Here names is of type string gentree Here names is of type string gentree

29 Programming in the large in ML Need mechanisms for Need mechanisms for Modularization Modularization Information hiding Information hiding Parametrization of interfaces Parametrization of interfaces While retaining type inference While retaining type inference Modules: like packages / namespaces Modules: like packages / namespaces Signatures: like package specifications /Java interfaces Signatures: like package specifications /Java interfaces Functors: like generics with formal packages Functors: like generics with formal packages

30 Structures structure Complex = struct type t = real * real; type t = real * real; val zero = (0.0, 0.0):t; (*qualification may be needed *) val zero = (0.0, 0.0):t; (*qualification may be needed *) val i = (0.0, 1.0); val i = (0.0, 1.0); fun sum ((x, y), (x1, y1)) = (x+x1, y+y1):t; fun sum ((x, y), (x1, y1)) = (x+x1, y+y1):t; fun prod ((x, y), (x1, y1)) = (x*x1 – y*y1, x*y1 + y*x1):t; fun prod ((x, y), (x1, y1)) = (x*x1 – y*y1, x*y1 + y*x1):t; fun inv ((x, y)) = fun inv ((x, y)) = let val den = x*x + y+y let val den = x*x + y+y in in (x / den, ~ y / den): t (x / den, ~ y / den): t end; end; fun quo (z, z1) = prod (z, inv (z1)):t; fun quo (z, z1) = prod (z, inv (z1)):t; end;

31 Using a structure use (“complex.ml”); > signature Complex : Complex.prod (Complex.i, Complex.i); > val it = (~1.0, 0.0); > val it = (~1.0, 0.0); val pi4 = (0.707, 0.707); > val pi4 … real * real structural equivalence Complex.prod (pi4, pi4); > val it = … : Complex.t;

32 Signatures Interface description without implementation: Interface description without implementation: signature CMPLX = signature CMPLX = sig sig type t type t val zero : t val zero : t val i : t val i : t val sum : t * t -> t val sum : t * t -> t val diff : t * t -> t val diff : t * t -> t val prod : t * t -> t val prod : t * t -> t val inv : t -> t val inv : t -> t val quo : t * t -> t val quo : t * t -> t end end

33 Multiple implementations structure complex1 : CMPLX = struct type t = real*real; (* cartesian representation *) type t = real*real; (* cartesian representation *) val zero = (0.0, 0.0); val zero = (0.0, 0.0); val i = (0.0, 1.0); val i = (0.0, 1.0);… Structure ComplexPolar: CMPLX = Struct type t = real*real (*polar representation*) type t = real*real (*polar representation*) val zero = (0.0, 0.0); val zero = (0.0, 0.0); val pi = 3.141592; val pi = 3.141592; val i := (0.0, pi / 2.0); val i := (0.0, pi / 2.0);

34 Information Hiding Only signature should be visible Only signature should be visible Declare structure to be opaque: Declare structure to be opaque: structure polar :> CMPLX = …. structure polar :> CMPLX = …. (Structure can be opaque or transparent depending on context). (Structure can be opaque or transparent depending on context). Can export explicit constructors and equality for type. Otherwise, equivalent to limited private types in Ada. Can export explicit constructors and equality for type. Otherwise, equivalent to limited private types in Ada. Can declare as eqtype to export equality Can declare as eqtype to export equality

35 Functors Structures and signatures are not first-class objects. Structures and signatures are not first-class objects. A program (structure) can be parametrized by a signature A program (structure) can be parametrized by a signature functor testComplex (C : CMPLX) = functor testComplex (C : CMPLX) = struct struct open C; (*equivalent to use clause*) open C; (*equivalent to use clause*) fun FFT.. fun FFT.. end; end; structure testPolar = testComplex (Complexpolar); structure testPolar = testComplex (Complexpolar); (* equivalent to instantiation with a package *) (* equivalent to instantiation with a package *)

36 Imperative Programming in ML A real language needs operations with side effects, state, and variables A real language needs operations with side effects, state, and variables Need to retain type inference Need to retain type inference - val p = ref 5; - val p = ref 5; val p = ref 5 : int ref ; (* ref is a type constructor*) val p = ref 5 : int ref ; (* ref is a type constructor*) - !p * 2; (* dereference operation *) - !p * 2; (* dereference operation *) val it = 10: int; val it = 10: int; - p := !p * 3; - p := !p * 3; val it = ( ) : unit (* assignment has no value *) val it = ( ) : unit (* assignment has no value *) References are equality types (pointer equality)

37 The library Lists structure: useful Operations on Lists Lists structure: useful Operations on Lists Listpairs structure, operates on 2 lists,e.g. Listpairs structure, operates on 2 lists,e.g. ListPair.unzip : ('a * 'b) list -> 'a list * 'b list ListPair.zip : 'a list * 'b list -> ('a * 'b) list ListPair.unzip : ('a * 'b) list -> 'a list * 'b list ListPair.zip : 'a list * 'b list -> ('a * 'b) list Vector operations (allow indexing) Vector operations (allow indexing)

38 Lazy Evaluation Delay evaluation till value needed Delay evaluation till value needed Some languages do this by default (Haskel and Miranda are examples) Some languages do this by default (Haskel and Miranda are examples) Advantage is avoiding evaluating stuff that is never needed, and aids correctness Advantage is avoiding evaluating stuff that is never needed, and aids correctness fun take x y = y fun take x y = y Are the following equivalent? Are the following equivalent? expr1 expr1 Take expr2 expr1 Take expr2 expr1 No, because expr2 might not terminate No, because expr2 might not terminate ML is strict (but laziness can be simulated using functional forms that delay evaluation) ML is strict (but laziness can be simulated using functional forms that delay evaluation) There are forms that assist in this approach (e.g. delayed) There are forms that assist in this approach (e.g. delayed)

39 Abstract Data Types abstype 'a set = null | ins of 'a * 'a set with val emptyset = null val addset = ins fun memberset (x, null) = false | memberset (x, ins (v, s)) = x = v orelse memberset (x, s) local fun subset (null, _) = true | subset (ins (x, s1), s2) = memberset (x, s1) andalso subset (s1, s2) in fun equalset (s1, s2) = subset (s1, s2) andalso subset (s2, s1) end end; abstype 'a set = null | ins of 'a * 'a set with val emptyset = null val addset = ins fun memberset (x, null) = false | memberset (x, ins (v, s)) = x = v orelse memberset (x, s) local fun subset (null, _) = true | subset (ins (x, s1), s2) = memberset (x, s1) andalso subset (s1, s2) in fun equalset (s1, s2) = subset (s1, s2) andalso subset (s2, s1) end end;


Download ppt "Introduction to ML A Quasi-Functional Language With Strong Typing And Type Inference."

Similar presentations


Ads by Google