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 c = 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; interface 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 set of prime numbers“, but doing so will not terminate in an eager language Streams solve both problems
Stream Example stream
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 data structure 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