<<

Language features

Side-effect freedom (no assignments) Higher-order functions SML Polymorphism Abstract data types and modules Pattern-based function definition Strong typing/optional declarations/type inferencing Applicative-order reduction Lexical binding

Value declaration Function declaration

- val pi = 3.14159; - fun upper = chr(ord c – 32); val pi = 3.14159 : real val upper : fn char -> char Notes: Notes: œ Parentheses around parameters optional - First line is what user types œ Only one formal argument allowed, but it could be a tuple - Second line is returned by interpreter œ The fn denotes a function type, i.e., a lambda œ Types are inferred • 32 is an int; ord returns an int; • hence c must be a char

Type inferencing Type inferencing(continued)

SML is a strongly typed, polymorphic language Proceeds from known information This means compiler must be able to determine all Known information includes types of literals and signatures of built-in functions types at compile time E.g.: fun increment x = x + 1; But some types may be variable (determined by actual arguments supplied) Compiler knows that 1 is an int and that one definition of the function ”+‘ maps pairs of int to int. No separate pre-processing steps, as with C++ Hence, it can infer that x is of type int and that templates or Adagenerics increment has type: fn : int -> int

1 Type inferencing(continued) Polymorphism

There is not always enough information to Functions may be defined with arguments whose completely resolve a type types are unknown until use E.g.: fun square x = x * x; E.g.: fun id x = x; Returns: val id : fn ‘a -> ‘a This declaration is ambiguous, because operator ”*‘ is overloaded Quoted symbol (i.e., ‘a) is a type variable

Fixed by providing partial type information: Any type can be substituted for this variable: œ e.g., fun square (x : real) = x * x; œ Thus id denotes a family of functions œ I.e., id is polymorphic œ or fun square x = (x : real) * x;

Examples of use Polymorphism (continued)

- id(3); More interesting function: val it = 3 : int fun reverse L = if L = nil then nil - id(#”z”); else reverse tail L @ [hd L]; val it = #”z” : char val reverse = fn : ‘a list -> ‘a list - id(square) Example use: reverse [1, 2, 3]; val it = fn : real -> real val it = [3, 2, 1] : int list

Function definition using patterns Examples (contrived)

Functions may be defined using patterns: Difference between positive integers: fun reverse nil = nil fun diff (0,y) = y | reverse (x::xs) = reverse xs @[x]; | diff (x,0) = x | diff (x,y) = diff (x-1, y-1); Each line defines one possible invocation pattern Smaller of two positive integers: Patterns may contain identifiers, wildcards (”_‘), fun smaller (0,_) = 0 lists, tuples, and records | smaller (_,0) = 0 | smaller (x,y) = 1 + smaller(x-1, y-1);

2 Limitations on pattern language Higher-order functions

Pattern-matching limited in sophistication: Function that integrates using trapezoid rule: - fun lesser (x,x) = x - fun trap (a, b, n, F) = | lesser (x,y) = smaller(x,y); if n <= 0 orelse b – a <= 0.0 then 0.0 else let Interpreter will complain about x being val delta = (b-a)/real(n) declared twice in the first pattern. in delta * (F(a) + F(a+delta))/2.0 + trap(a+delta, b, n-1, F) end;

Example of use Exercise

- fun myfun x = x * 1.5; W hat is the type of the trap function? val myfun = fn : real -> real - trap(0.0, 3.0, 30, lin); val it = 6.75 : real

Functional composition Exercise

Composition is —built in“ to SML with the —o“ Suppose functional composition were not operator built into SML. Define a polymorphic functional-composition function. Example: - fun increment x = x + 1; val increment = fn : int -> int - (increment o increment) 3; val it = 5 : int

3 Answer (one of many) Lambda expressions fun comp (F,G) = W e can define λ-expressions in SML: let (λ x. B) fun C(x) = F(G(x)) is equivalent to in (fn x => B) C Example: end; val increment = (fn x => x + 1)

Exercise Data types

Redefine the functional-composition function Built-in primitive data types: integer, real, boolean, using fn rather than let string Structuring mechanisms: lists, tuples, functions Extension mechanisms: œ Type abbreviations type vector = real list; œ Polymorphic types type (’d,’r) mapping = (’d * ’r) list val foo = [(“c”,6), (“a”,1)] : (string, int) mapping

Concrete data types Type constructors datatype fruit = Apple | Grape | Pear; Type constructors can be polymorphic, allowing datatype fruit definition of container types con Apple : fruit datatype (’a,’b) element = con Grape : fruit P of ’a * ’b | con Pear : fruit S of ’a; datatype (’a,’b) element Think of fruit as an enumeration type con P : ’a * ’b -> (’a, ’b) element con S : ’a -> (’a,’b) element Apple, Grape, and Pear construct elements of type fruit [P(“in”,6), S(“function”), P(“as”,2)] : (string, int) element list

4 Polymorphic binary tree Example datatype ’a tree = fun frontier (empty) = [] empty | | frontier (leaf x) = [x] leaf of ’a | | frontier (node (t1,t2)) = node of ’a tree * ’a tree; frontier(t1) @ frontier(t2); datatype ’a tree val frontier = fn : ’a tree -> ’a list con empty : ’a tree con leaf : ’a -> ’a tree con node : ’a tree * ’a tree -> ’a tree

Example (continued) Abstract data types val myTree = node( leaf(“a”), node( leaf(“b”), Abstract data types are types together with leaf(“c”))); the functions that operate on them frontier myTree; val it = [“a”, “b”, “c”] : string list Typically the implementation of the type is hidden from clients; only the access routines are visible SML offers this capability with abstypes

Abstypespecification Example: Association list ADT abstype T = R abstype (’a,’b) Assoc = assoc of (’a*’b) list with with exception not_found; local declarations fun lookup’ (x, nil) = raise not_found end; | lookup’ (x, (y,z)::rest) = if x = y then z Here: else lookup’(x,rest) œ T is the name of the abstract type in fun lookup (x, assoc A) = lookup’(x,A) œ R is the representation type,i.e., the type used to end; implement objects of type T fun update (a, b, assoc A) = assoc((a,b)::A); œ Declarations between with and end constitute the val empty = assoc nil; of T end;

5 Alternative implementation of Examples of use Assoc ADT val a = update(3, “three”, empty); abstype (’a,’b) Assoc = assoc of (’a -> ’b) val a = [abstype] : (int, string) Assoc with exception not_found; val b = update(2, “two”, a); fun lookup (x, assoc A) = A x; val b = [abstype] : (int, string) Assoc fun update (a, b, assoc A) = (fn x => if x =a then b else A x) lookup (2,b); val empty = (fn x => raise not_found); “two” : string end;

Two implementations of Assoc Streams

Are indistinguishable to client programs Functional languages have a problem with I/O, which inherently has side effects Yet each uses a fundamentally different type to represent Assoc objects Eager languages, such as ML, have a further problem related to abstraction over infinite sets: œ W e would like to declare and manipulate sets, such as —the of prime numbers“, but doing so will not terminate in an eager language Streams solve both problems

Stream Example

Defn: A pair, abstype ’a stream = stream of unit -> (’a * ’a stream) œ First element is head of the infinite list with œ Second element is a stream that returns the fun next (stream f) = f (); rest of the values val mkStream = stream Stream is a recursive end; Implemented in SML by having second fun natural n = element of the pair be a function that mkStream (fn () => (n, natural(n+1))); returns a stream val natural = fn : int -> int stream

6 Example stream (continued) Infinite stream of primes

val primes = val s = natural 0; let fun nextPrime(n,L) = val s = - : int stream; let fun check(n,[]) = n | check(n, h::t) = val (first,rest) = next s; if n mod h = 0 then check(n+1,L) else check(n,t) val first = 0 : int in check(n,L) end val rest = - : int stream fun primestream (n,L) = mkStream (fn () => let val m = nextPrime(n,L) val (second, rest2) = next rest; in (m, primestream (m+1,m::L)) val second = 1 : int end) in primestream(2,[]) val rest2 = - : int stream end;

Examples of use Modularity in ML

val (first,rest) = next primes; ML allows modulesthat aggregate types, val first = 2 : int functions, and exceptions into meaningful val rest = - : int stream units that can be represented as wholes. val (second, r2) = next rest; val second = 3 : int Each module comprises two parts: val r2 = - : int stream œ A signature,which specifies the interface of the module, and val (third, r3) = next r2; val third = 5 : int œ A structure, which specifies the implementation of the module val r3 = - : int stream

Example Example (continued) signature RationalNumbersSig = structure RationalNumbers : RationalNumbersSig = sig struct type Rational = { Numer : int, Denom : int } type Rational fun Num ({Numer=x, Denom=_}:Rational) = x val Eq: Rational * Rational -> Rational fun Den ({Numer=_, Denom=x}:Rational) = x val Add: Rational * Rational -> Rational fun Eq (x,y) = (Num(x) * Den(y) = Num(y) * Den(x)) val Mult: Rational * Rational -> Rational fun Add (x,y) : Rational = end {Numer = Num(x)*Den(y) + Num(y)*Den(x), Denom = Den(y)*Den(x)} fun Mult (x,y) : Rational = {Numer = Num(x)*Num(y), Denom = Den(x)*Den(y)} end

7