CSCC24 — Functional Programming — Typing, Scope, Exceptions— ML Carolyn MacLeod 1 winter 2012 1 Based on slides by Anya Tafliovich, with many thanks to Gerald Penn and Sheila McIlraith. 1
CSCC24 — Functional Programming — Typing,Scope, Exceptions— ML
Carolyn MacLeod1
winter 2012
1Based on slides by Anya Tafliovich, with many thanks to Gerald Penn andSheila McIlraith.
1
motivation
Consider the following Scheme function definition:
(define foobar
(lambda (x)
(if (even? x)
(first x)
(rest x))))
Anything wrong?
even? expects a number andfirst, rest expect a list=⇒ we’ll get an error at run-time.
We are able to figure it out before run-time. Can’t a PL supportthis kind of reasoning?
2
motivation
Consider the following Scheme function definition:
(define foobar
(lambda (x)
(if (even? x)
(first x)
(rest x))))
Anything wrong?
even? expects a number andfirst, rest expect a list=⇒ we’ll get an error at run-time.
We are able to figure it out before run-time. Can’t a PL supportthis kind of reasoning?
2
motivation
Consider the following Scheme function definition:
(define foobar
(lambda (x)
(if (even? x)
(first x)
(rest x))))
Anything wrong?
even? expects a number andfirst, rest expect a list=⇒ we’ll get an error at run-time.
We are able to figure it out before run-time. Can’t a PL supportthis kind of reasoning?
2
What is the property of Scheme that is related to this property
Dynamic typing
What other language do you know with dynamic typing?
Python
3
What is the property of Scheme that is related to this property
Dynamic typing
What other language do you know with dynamic typing?
Python
3
What is the property of Scheme that is related to this property
Dynamic typing
What other language do you know with dynamic typing?
Python
3
What is the property of Scheme that is related to this property
Dynamic typing
What other language do you know with dynamic typing?
Python
3
motivation
• It could be worse!
• At least Scheme checks that x is a list (cons pair) beforeaccessing memory in the execution of (rest x) .
• In fact, Scheme is type safe: it will never execute (op arg)
if op is not applicable to arg .
• What’s a language you know that is not type safe?
• C is one you most likely know.
• Languages which are not type safe (e.g., C) allow unsafememory accesses: a major source of security vulnerabilities.
4
motivation
• It could be worse!
• At least Scheme checks that x is a list (cons pair) beforeaccessing memory in the execution of (rest x) .
• In fact, Scheme is type safe: it will never execute (op arg)
if op is not applicable to arg .
• What’s a language you know that is not type safe?
• C is one you most likely know.
• Languages which are not type safe (e.g., C) allow unsafememory accesses: a major source of security vulnerabilities.
4
motivation
• It could be worse!
• At least Scheme checks that x is a list (cons pair) beforeaccessing memory in the execution of (rest x) .
• In fact, Scheme is type safe: it will never execute (op arg)
if op is not applicable to arg .
• What’s a language you know that is not type safe?
• C is one you most likely know.
• Languages which are not type safe (e.g., C) allow unsafememory accesses: a major source of security vulnerabilities.
4
motivation
• It could be worse!
• At least Scheme checks that x is a list (cons pair) beforeaccessing memory in the execution of (rest x) .
• In fact, Scheme is type safe: it will never execute (op arg)
if op is not applicable to arg .
• What’s a language you know that is not type safe?
• C is one you most likely know.
• Languages which are not type safe (e.g., C) allow unsafememory accesses: a major source of security vulnerabilities.
4
motivation
• It could be worse!
• At least Scheme checks that x is a list (cons pair) beforeaccessing memory in the execution of (rest x) .
• In fact, Scheme is type safe: it will never execute (op arg)
if op is not applicable to arg .
• What’s a language you know that is not type safe?
• C is one you most likely know.
• Languages which are not type safe (e.g., C) allow unsafememory accesses: a major source of security vulnerabilities.
4
motivation
• It could be worse!
• At least Scheme checks that x is a list (cons pair) beforeaccessing memory in the execution of (rest x) .
• In fact, Scheme is type safe: it will never execute (op arg)
if op is not applicable to arg .
• What’s a language you know that is not type safe?
• C is one you most likely know.
• Languages which are not type safe (e.g., C) allow unsafememory accesses: a major source of security vulnerabilities.
4
motivation
• Want to catch all errors at compile-time.
• Extremely difficult, if not impossible.
• Identify a certain class of errors and guarantee to catch all ofthem.
• Catch as many errors as possible?
• Costly: programmers’ expertise, computational resources, etc.• Slower development, less expressive languages.• Used in development of safety-critical systems.
• Compromise: type systems.
• Guarantees range widely: from basic safety of memory accessesto just about anything you want.
5
motivation
• Want to catch all errors at compile-time.• Extremely difficult, if not impossible.
• Identify a certain class of errors and guarantee to catch all ofthem.
• Catch as many errors as possible?
• Costly: programmers’ expertise, computational resources, etc.• Slower development, less expressive languages.• Used in development of safety-critical systems.
• Compromise: type systems.
• Guarantees range widely: from basic safety of memory accessesto just about anything you want.
5
motivation
• Want to catch all errors at compile-time.• Extremely difficult, if not impossible.
• Identify a certain class of errors and guarantee to catch all ofthem.
• Catch as many errors as possible?
• Costly: programmers’ expertise, computational resources, etc.• Slower development, less expressive languages.• Used in development of safety-critical systems.
• Compromise: type systems.
• Guarantees range widely: from basic safety of memory accessesto just about anything you want.
5
motivation
• Want to catch all errors at compile-time.• Extremely difficult, if not impossible.
• Identify a certain class of errors and guarantee to catch all ofthem.
• Catch as many errors as possible?
• Costly: programmers’ expertise, computational resources, etc.• Slower development, less expressive languages.• Used in development of safety-critical systems.
• Compromise: type systems.
• Guarantees range widely: from basic safety of memory accessesto just about anything you want.
5
motivation
• Want to catch all errors at compile-time.• Extremely difficult, if not impossible.
• Identify a certain class of errors and guarantee to catch all ofthem.
• Catch as many errors as possible?• Costly: programmers’ expertise, computational resources, etc.
• Slower development, less expressive languages.• Used in development of safety-critical systems.
• Compromise: type systems.
• Guarantees range widely: from basic safety of memory accessesto just about anything you want.
5
motivation
• Want to catch all errors at compile-time.• Extremely difficult, if not impossible.
• Identify a certain class of errors and guarantee to catch all ofthem.
• Catch as many errors as possible?• Costly: programmers’ expertise, computational resources, etc.• Slower development, less expressive languages.
• Used in development of safety-critical systems.
• Compromise: type systems.
• Guarantees range widely: from basic safety of memory accessesto just about anything you want.
5
motivation
• Want to catch all errors at compile-time.• Extremely difficult, if not impossible.
• Identify a certain class of errors and guarantee to catch all ofthem.
• Catch as many errors as possible?• Costly: programmers’ expertise, computational resources, etc.• Slower development, less expressive languages.• Used in development of safety-critical systems.
• Compromise: type systems.
• Guarantees range widely: from basic safety of memory accessesto just about anything you want.
5
motivation
• Want to catch all errors at compile-time.• Extremely difficult, if not impossible.
• Identify a certain class of errors and guarantee to catch all ofthem.
• Catch as many errors as possible?• Costly: programmers’ expertise, computational resources, etc.• Slower development, less expressive languages.• Used in development of safety-critical systems.
• Compromise: type systems.
• Guarantees range widely: from basic safety of memory accessesto just about anything you want.
5
motivation
• Want to catch all errors at compile-time.• Extremely difficult, if not impossible.
• Identify a certain class of errors and guarantee to catch all ofthem.
• Catch as many errors as possible?• Costly: programmers’ expertise, computational resources, etc.• Slower development, less expressive languages.• Used in development of safety-critical systems.
• Compromise: type systems.• Guarantees range widely: from basic safety of memory accesses
to just about anything you want.
5
typing
A type is:
“A name for a set of values and some operations which can beperformed on that set of values.”
“A collection of computational entities that share some commonproperty.”
6
typing
Some examples of types (in ML notation):
• int : the integers (and their operations).
• real : the real numbers (and their operations).
• string : the strings (and their operations).
• bool : the booleans (and their operations).
• int → bool : the functions that take integers as input andreturn booleans as output.
What constitutes a type is language dependent.
7
typing
Some examples of types (in ML notation):
• int : the integers (and their operations).
• real : the real numbers (and their operations).
• string : the strings (and their operations).
• bool : the booleans (and their operations).
• int → bool : the functions that take integers as input andreturn booleans as output.
What constitutes a type is language dependent.
7
typing
Benefits of having a type system:
• Easier to debug programs: compiler can catch many errors.
• Static analysis: a lot of useful information about the programcan be obtained at compile-time.
• Efficiency: typing can be used by the compiler to generatequicker code.
• Correctness: typing can be used (by the programmer or by thecompiler) to prove correctness of code.
• Documentation: types declare your intent with well-chosennames.
8
Recall - last lecture
We saw this Scheme function definition:
(define foobar
(lambda (x)
(if (even? x)
(first x)
(rest x))))
What is wrong with the code?
What programming language feature would stop us creatingprograms with this kind of bugs?
9
Recall - last lecture
We saw this Scheme function definition:
(define foobar
(lambda (x)
(if (even? x)
(first x)
(rest x))))
What is wrong with the code?
What programming language feature would stop us creatingprograms with this kind of bugs?
9
typing
A programming language is type safe if no program is allowed toviolate its type distinctions.
The process of verifying and enforcing the constraints of types iscalled type checking.
Type checking can either occur at compile-time (static typechecking) or at run-time (dynamic type checking).
10
static vs dynamic typing
Dynamic type checking:
• Performed at run-time.
• Slower execution: need to carry type information around, lotsof run-time checks.
• More flexible.
• Easier refactoring.
Static type checking:
• Faster execution.
• Compiler can do a lot of optimization.
• Some argue that resulting programs are safer.
• Some argue that resulting programs are more elegant andmodular.
• Some argue that programmers will write horrible code to getaround a static type-checker.
11
static vs dynamic typing
Dynamic type checking:
• Performed at run-time.
• Slower execution: need to carry type information around, lotsof run-time checks.
• More flexible.
• Easier refactoring.
Static type checking:
• Faster execution.
• Compiler can do a lot of optimization.
• Some argue that resulting programs are safer.
• Some argue that resulting programs are more elegant andmodular.
• Some argue that programmers will write horrible code to getaround a static type-checker.
11
static typing
Explicit static typing: code contains type annotations.
• For example, in Java:• Variable declarationsint x,y,z;
• Function headerspublic static void main(String[] arg)
Type inference: infer all types from the code that does not containexplicit type annotations.
• fun foo(x,y,z) = if x then y + z + 1.5 else 0.0;
• x must be boolean
• y, z must be reals
• the return value is real
• foo : bool * real * real -> real
12
static typing
Explicit static typing: code contains type annotations.
• For example, in Java:• Variable declarationsint x,y,z;
• Function headerspublic static void main(String[] arg)
Type inference: infer all types from the code that does not containexplicit type annotations.
• fun foo(x,y,z) = if x then y + z + 1.5 else 0.0;
• x must be boolean
• y, z must be reals
• the return value is real
• foo : bool * real * real -> real
12
static typing
Explicit static typing: code contains type annotations.
• For example, in Java:• Variable declarationsint x,y,z;
• Function headerspublic static void main(String[] arg)
Type inference: infer all types from the code that does not containexplicit type annotations.
• fun foo(x,y,z) = if x then y + z + 1.5 else 0.0;
• x must be boolean
• y, z must be reals
• the return value is real
• foo : bool * real * real -> real
12
static typing
Explicit static typing: code contains type annotations.
• For example, in Java:• Variable declarationsint x,y,z;
• Function headerspublic static void main(String[] arg)
Type inference: infer all types from the code that does not containexplicit type annotations.
• fun foo(x,y,z) = if x then y + z + 1.5 else 0.0;
• x must be boolean
• y, z must be reals
• the return value is real
• foo : bool * real * real -> real
12
static typing
Explicit static typing: code contains type annotations.
• For example, in Java:• Variable declarationsint x,y,z;
• Function headerspublic static void main(String[] arg)
Type inference: infer all types from the code that does not containexplicit type annotations.
• fun foo(x,y,z) = if x then y + z + 1.5 else 0.0;
• x must be boolean
• y, z must be reals
• the return value is real
• foo : bool * real * real -> real
12
static typing
Explicit static typing: code contains type annotations.
• For example, in Java:• Variable declarationsint x,y,z;
• Function headerspublic static void main(String[] arg)
Type inference: infer all types from the code that does not containexplicit type annotations.
• fun foo(x,y,z) = if x then y + z + 1.5 else 0.0;
• x must be boolean
• y, z must be reals
• the return value is real
• foo : bool * real * real -> real
12
Features of Standard ML
What is ML?
ML is an ALGOL family language, a child of Pascal and Lisp, anda distant cousin of C
ML was designed as the Meta- Language of the Logic forComputable Functions (LCF) System. Its original purpose was forwriting programs that would attempt to construct mathematicalproofs.
Question - What have we seen that is similar?
ML is a functional language, although ML can be written in amore imperative style. Arguably, you can call it a mostly functionallanguage, or a function-oriented imperative language.
13
Features of Standard ML
• Modular:Supports modules and interfaces, determining whatcomponents and types from the module are visible outside.
• Strict Pass By Value: The arguments of the function areevaluated before the beginning of the function beingevaluated.
• Polymorphic: Supports both data type and functionpolymorphism.
• Type System: The ML type system is considered one of thecleanest in any language
• Static typing• Type inference• Type safe
• Functions are first class values: Functions can be defined,passed as arguments, and returned as function results.
• Garbage Collection
• Exception Handling
14
Standard ML
A few examples
15
ML Examples
(* This is a comment in SML *)
fun factorial (n:int):int =
if n = 0
then 1
else n * factorial(n-1)
Notice, we use ”=” both for comparison, and for the functiondefinition. We use an infix notation, unlike in Scheme. In thisexample, we have explicit static typing.
16
ML Examples
Another form of writing function definitions:
(* Our first fibonacci solution in Scheme,
translated to ML *)
fun fib 1 = 1
| fib 2 = 1
| fib (n) = fib(n-1) + fib(n-2)
This is also an example of type inference - notice that this versiondoes not specify types for the input and output of fib, however,when compiled or entered into the interpreter, it gives a typesignature of
val fib = fn : int -> int
17
type inference
Trade-off:
• Want the language to be expressive.
• Want tractable type inference.
ML is designed to make type inference tractable.
Widely regarded as an important language innovation.
18
ML data types
Basic types:
• unit : the only member is ().
• bool : booleans.
• int : integers.
• real : reals.
• string : strings.
More types:
• (〈type0〉 ∗ 〈type1〉 ∗ . . . ∗ 〈typen〉) : tuples.
• 〈type〉 list : lists.
• 〈input-type〉 → 〈output-type〉 : functions.
19
ML data types
Basic types:
• unit : the only member is ().
• bool : booleans.
• int : integers.
• real : reals.
• string : strings.
More types:
• (〈type0〉 ∗ 〈type1〉 ∗ . . . ∗ 〈typen〉) : tuples.
• 〈type〉 list : lists.
• 〈input-type〉 → 〈output-type〉 : functions.
19
ML basic types
unit: this type has only one element ()
- ();
val it = () : unit
ML assigns the last evaluated value to the special variable it.Reading the above snippet of the ML interpreter session:
• evaluate (), please
• the special variable it now has the value () of type unit
20
ML basic types
bool: this type has two elements: true and false.
Operations on bools: not, toString, ...
Special operators: andalso, orelse.
For example:
- if (not true andalso false) orelse true
then true
else false;
val it true : bool
21
ML basic types
The structure of lists in ML is the same as in Scheme and Prolog.Strings must have elements of all the same type.
- hd [1,2,3];
val it = 1 : int
- tl [1,2,3];
val it = [2,3] : int list
- "a" :: ["b","c"];
val it = ["a","b","c"] : string list
- [1,2]@[3];
val it = [1,2,3] : int list
These are the equivalents of first/car, rest/cdr, and cons inScheme. The ”@” operator is used like append in Scheme.
22
ML basic types
fun elem_to_list [] = []
| elem_to_list (h::t) =
[h] :: (elem_to_list t)
23
ML basic types
int: {... ˜2, ˜1, 0 , 1 , 2, ... }
Operations: +, −, ∗, ˜, div, mod, <, >, =, <=, >=, <>
For example:
- 5 + 6 * 2 - 3 div 2;
val it = 16 : int
- 5 mod 2 >= 6 mod 2;
val it = true : bool
24
ML basic types
int: {... ˜2, ˜1, 0 , 1 , 2, ... }Note the use of ~ for negation. Here’s why: - is a binaryoperator while ~ is a unary operator.
- -5;
stdIn:27.1 Error: expression or pattern begins
with infix identifier "-"
stdIn:27.1-27.3 Error: operator and operand don’t
agree [literal]
operator domain: ’Z * ’Z
operand: int
in expression:
- 5
- ~5;
val it = ~5 : int
25
ML basic types
real: { 1.0, 3.14159, 11.7, .... }
Operations: +, −, ∗, <, >, <=, >=
Note: You cannot mix reals and integers in one expression.
Every ML expression has a type. If an expression cannot be typed,we get an error.
26
ML basic types
For example:
- 2 - 1.5;
stdIn:43.1-43.8 Error: operator and operand
don’t agree
operator domain: int * int
operand: int * real
in expression:
2 - 1.5
Understanding the error message...
Why int ? For mathematical operators, if the type is notspecified (or inferred), then type int is assumed.
27
ML basic types
More examples:
- 2;
val it = 2 : int
- 2.0;
val it = 2.0 : real
- real(2);
val it = 2.0 : real
- 2.0 - 1.5;
val it = 0.5 : real
- real(2) - 1.5;
val it = 0.5 : real
28
ML basic types
Note that SML inspects the leftmost argument first. For example:
- 1.5 + 2;
stdIn:1.1-2.3 Error: operator and operand
don’t agree
operator domain: real * real
operand: real * int
in expression:
1.5 + 2
Now it expects two reals.
29
ML basic types
Note that while <, <=, >, >= are defined for all numeric types,= and <> are, for example, not defined for reals. In fact,
= : ’’a * ’’a -> bool
<> : ’’a * ’’a -> bool
In ML ’’a means the type for which equality is defined.
With reals we can use a <= b andalso b >= a.
Or, much better, use real arithmetic:
- Real.==(1.0, 1.0);
val it = true : bool
- Real.==(1.0, 1.00000);
val it = true : bool
30
ML basic types
string: { ”Hello, world”, ”CSCC24 is fun!”, ... }
Operations: ^ for concatenation
For example:
- "Hello";
val it = "Hello" : string
- "Hello" ^ " " ^ "Jim";
val it = "Hello Jim" : string
31
ML data types
Basic types:
• unit : the only member is ().
• bool : booleans.
• int : integers.
• real : reals.
• string : strings.
More types:
• (〈type0〉 ∗ 〈type1〉 ∗ . . . ∗ 〈typen〉) : tuples.
• 〈type〉 list : lists.
• 〈input-type〉 → 〈output-type〉 : functions.
32
ML data types
Basic types:
• unit : the only member is ().
• bool : booleans.
• int : integers.
• real : reals.
• string : strings.
More types:
• (〈type0〉 ∗ 〈type1〉 ∗ . . . ∗ 〈typen〉) : tuples.
• 〈type〉 list : lists.
• 〈input-type〉 → 〈output-type〉 : functions.
32
ML types
A tuple packs together several types.
- ("foo", "bar");
val it = ("foo","bar") : string * string
- ("foo", "bar", 123);
val it = ("foo","bar",123) : string * string * int
- ("foo", (123, 456));
val it = ("foo",(123,456)) : string * (int * int)
Operations: accessing component N of a tuple t is written #N t.
- #2 ("foo", 123, 3.14);
val it = 123 : int
33
ML types
In ML all elements in a list must have the same type.
- [1,2,3,4];
val it = [1,2,3,4] : int list
- ["cscc24","is","fun"];
val it = ["cscc24","is","fun"] : string list
- [("foo",1.0),("bar",3.14)];
val it = [("foo",1.0),("bar",3.14)] : (string * real) list
[ ] (or nil) is the empty list.
Constructor: ::
Selectors: hd, tl
More operations: @, null, length, map, foldr, ...
34
ML types
Some examples:
- "foo"::["bar","foobar"];
val it = ["foo","bar","foobar"] : string list
- [1,2]@[3,4];
val it = [1,2,3,4] : int list
- hd [1,2];
val it = 1 : int
- tl [1,2];
val it = [2] : int list
35
ML syntax
A variable declaration in ML looks like:
• val 〈name〉 = 〈expr〉
• val x = 42 + 24
• (define x (+ 42 24))
36
ML syntax
A variable declaration in ML looks like:
• val 〈name〉 = 〈expr〉• val x = 42 + 24
• (define x (+ 42 24))
36
ML syntax
A variable declaration in ML looks like:
• val 〈name〉 = 〈expr〉• val x = 42 + 24
• (define x (+ 42 24))
36
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉
• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉
• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉
• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.
This argumentcould be a tuple.
37
ML functions
The syntax for anonymous functions in ML is:
• fn 〈arg〉 => 〈body〉• fn x => x + 1
• (lambda (x) (+ x 1))
Giving a name to a function:
• val 〈name〉 = fn 〈arg〉 => 〈body〉• val inc = fn x => x + 1
• (define inc (lambda (x) (+ x 1)))
Or:
• fun 〈name〉 〈arg〉 = 〈body〉• fun inc x = x + 1
• (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
37
ML types
The type of a function is determined by the type of the input andthe type of the output.
Some examples:
- fun inc x = x + 1;
val inc = fn : int -> int
- fun addInc(x,y) = x + y + 1;
val addInc = fn : int * int -> int
- fun optInc(x,y,c) = if c then x + y + 1 else x + y;
val optInc = fn : int * int * bool -> int
38
parametric polymorphism
What is the type of fn x => x ?
- fun id x = x;
val id = fn : ’a -> ’a
- id 324;
val it = 324 : int
- id 3.14;
val it = 3.14 : real
- id "foo";
val it = "foo" : string
- id [1,2,3];
val it = [1,2,3] : int list
- id (fn x => x + 1);
val it = fn : int -> int
39
parametric polymorphism
What is the type of fn x => x ?
- fun id x = x;
val id = fn : ’a -> ’a
- id 324;
val it = 324 : int
- id 3.14;
val it = 3.14 : real
- id "foo";
val it = "foo" : string
- id [1,2,3];
val it = [1,2,3] : int list
- id (fn x => x + 1);
val it = fn : int -> int
39
parametric polymorphism
- fun id x = x;
val id = fn : ’a -> ’a
The ’a in ML stands for α. It is a type variable.
id is a polymorphic function.
α→ α means “for every valid type α, α→ α”∀α · α→ α
When id is applied to 324 , the type variable α is instantiatedto int .
40
parametric polymorphism
- fun id x = x;
val id = fn : ’a -> ’a
The ’a in ML stands for α. It is a type variable.
id is a polymorphic function.
α→ α means “for every valid type α, α→ α”∀α · α→ α
When id is applied to 324 , the type variable α is instantiatedto int .
40
parametric polymorphism
- fun id x = x;
val id = fn : ’a -> ’a
The ’a in ML stands for α. It is a type variable.
id is a polymorphic function.
α→ α means “for every valid type α, α→ α”∀α · α→ α
When id is applied to 324 , the type variable α is instantiatedto int .
40
parametric polymorphism
- fun id x = x;
val id = fn : ’a -> ’a
The ’a in ML stands for α. It is a type variable.
id is a polymorphic function.
α→ α means “for every valid type α, α→ α”∀α · α→ α
When id is applied to 324 , the type variable α is instantiatedto int .
40
parametric polymorphism
Examples:
fun choose (a,b,c) = if a then b else c;
choose : bool * ’a * ’a -> ’a;
fun swap (x,y) = (y,x);
swap : ’a * ’b -> ’b * ’a;
41
parametric polymorphism
Examples:
fun choose (a,b,c) = if a then b else c;
choose : bool * ’a * ’a -> ’a;
fun swap (x,y) = (y,x);
swap : ’a * ’b -> ’b * ’a;
41
parametric polymorphism
Examples:
fun choose (a,b,c) = if a then b else c;
choose : bool * ’a * ’a -> ’a;
fun swap (x,y) = (y,x);
swap : ’a * ’b -> ’b * ’a;
41
parametric polymorphism
Examples:
fun choose (a,b,c) = if a then b else c;
choose : bool * ’a * ’a -> ’a;
fun swap (x,y) = (y,x);
swap : ’a * ’b -> ’b * ’a;
41
parametric polymorphism
What is the type of the following function?
fun length lst = if (null lst) then 0
else 1 + length (tl lst);
length : ’a list -> int
List is a polymorphic data type.
42
parametric polymorphism
What is the type of the following function?
fun length lst = if (null lst) then 0
else 1 + length (tl lst);
length : ’a list -> int
List is a polymorphic data type.
42
parametric polymorphism
What is the type of the following function?
fun length lst = if (null lst) then 0
else 1 + length (tl lst);
length : ’a list -> int
List is a polymorphic data type.
42
currying
With named functions:
- fun sum x y = x + y;
val sum = fn : int -> int -> int
- sum 2;
val it = fn : int -> int
- sum 2 3;
val it = 5 : int
43
currying
With anonymous functions:
- fn x => fn y => x + y;
val it = fn : int -> int -> int
- (fn x => fn y => x + y) 2;
val it = fn : int -> int
- (fn x => fn y => x + y) 2 3;
val it = 5 : int
44
pattern matching
Value declaration (general form): val <pat> = <exp>
- val myTuple = ("foo", "bar");
val myTuple = ("foo","bar") : string * string
- val (x,y) = myTuple;
val x = "foo" : string
val y = "bar" : string
- val myList = [1,2,3,4];
val myList = [1,2,3,4] : int list
- val h::r = myList;
stdIn:52.5-52.18 Warning: binding not exhaustive
h :: r = ...
val h = 1 : int
val r = [2,3,4] : int list45
pattern matching
“ ” is “don’t care”: matches everything, binds nothing
- val myTuple = (‘‘foo’’, ‘‘bar’’);
val myTuple = ("foo","bar") : string * string
- val (first, _) = myTuple;
val first = "foo" : string
- val h::_ = [1,2,3];
stdIn:66.5-66.19 Warning: binding not exhaustive
h :: _ = ...
val h = 1 : int
46
pattern matching
Function declaration with pattern matching:
fun <name> <pattern1> = <exp1>
| <name> <pattern2> = <exp2>
.
.
| <name> <patternN> = <expN>;
This means: the function name is name. It takes one argument (asany other ML function). It tries to match the argument topattern1. If it succeeds, it returns value of exp1. Otherwise, triesto match the argument to pattern2. Etc, etc.
47
pattern matching
For example we can (and should!) rewrite len to use patternmatching:
fun len [] = 0
| len (x::xs) = 1 + len xs;
len : ’a list -> int
Non-exhaustive patterns:
- fun len (x::xs) = 1 + len xs;
stdIn ... Warning: match nonexhaustive
x :: xs => ...
val len = fn : ’a list -> int
- len [1,2,3];
uncaught exception nonexhaustive match failure
raised at: ...
48
pattern matching
Function firstlist takes a list of pairs and returns the listconsisting of the first elements only. For example:
firstlist [] ==> []
firstlist [(1,2),(1,3)] ==> [1,1]
firstlist [(1,"a"),(2,"b"),(3,"c")] ==> [1,2,3]
firstlist [([],"a"),([1],"b"),([1,2],"c")] ==>
[[],[1],[1,2]]
fun firstlist [] = []
| firstlist ((e1,e2)::es) =
e1::(firstlist es);
firstlist : (’a * ’b) list -> ’a list
49
ML notes
Syntax of an ML let expression:
let
<declaration>
<declaration>
...
in
<expression>
end
50
ML notes
Example:
fun reverse lst =
let
fun reverseAcc [] acc = acc
| reverseAcc (x::xs) acc =
reverseAcc xs (x::acc)
in
reverseAcc lst []
end;
reverse : ’a list -> ’a list
- reverse [1,2,3];
val it = [3,2,1] : int list
51
ML notes
Function application is left-associative:
f g h == (f g) h
Example:
- reverse 1::[2,3];
... Error: operator and operand don’t agree
operator domain: ’Z list
operand: int
in expression:
reverse 1
- reverse (1::[2,3]);
val it = [3,2,1] : int list
52
type synonyms
We can give existing types new names. Syntax:
type new type = ty
new type becomes an alias (a synonym) for the existing type ty.
-type float = real;
type float = real
-type count = int
and average = real;
type count = int
type average = real
-val f : float = 2.3;
val f = 2.3: float
-val i = 3 : count;
val i = 3: count 53
type synonyms
But notice float, real, and average are all of the same basetype, i.e. real
-val f : float = 2.3;
val f = 2.3 : float
-val a = f : average;
val a = 2.3 : average
-val sum = a+f;
val sum = 4.6 : average
-val sum = f+a;
val sum = 4.6 : float
54
user defined datatypes
General Syntax:
datatype new_type =
Cons1 of type1
| Cons2 of type2
...
| ConsN of typeN
• Defines a new type called new type.
• type1,...,typeN are previously defined types.
• Cons1,...,ConsN are constructors. They are used to createa value of new type type.
• of type is omitted if a constructor does not need anyargument (such constructors are called constants).
55
enumerated types
All constructors are constants (no argument).Example:
-datatype color = Red | Blue | Green;
datatype color = Blue | Green | Red
-val c = Red;
val c = Red : color
-fun colorStr Red = "Red"
| colorStr Blue = "Blue"
| colorStr Green= "Green";
val colorStr = fn : color -> string
-colorStr c;
val it = "Red" : string
56
variant types
Create union of different types:
datatype number = R of real | I of int;
val n1 = I 2;
val n2 = R 3.0;
val lst = [R 2.2, I 3, I 4, R 0.1];
(* val lst = [R 2.2,I 3,I 4,R 0.1] : number list *)
57
variant types
datatype number = R of real | I of int;
val lst = [R 2.2, I 3, I 4, R 0.1];
fun sumInts [] = 0
| sumInts ((I x)::rest) = x + sumInts rest
| sumInts ((R x)::rest) = sumInts rest;
sumInts : number list -> int;
sumInts lst;
(* val it = 7 : int *)
58
recursive types
A datatype can be recursive: e.g. a linked list
datatype llist = Nil | Node of int * llist;
(* datatype llist = Nil | Node of int * llist *)
val x = Nil;
(* val x = Nil : llist *)
val y = Node (5,Nil);
(* val y = Node (5,Nil) : llist *)
val z = Node(3, Node(2, Node(1,Nil)));
(* val z = Node (3,Node (2,Node #)) : llist *)
59
recursive types
datatype llist = Nil | Node of int * llist;
(* datatype llist = Nil | Node of int * llist *)
(* length of a linked list *)
fun len Nil = 0
| len (Node (_,xs)) = 1 + len xs;
len : llist -> int
len z;
(* val it = 3 : int*)
60
recursive types
What about a polymorphic linked list?
datatype ’a llist = Nil | Node of ’a * (’a llist);
(* datatype ’a llist = Nil | Node of ’a * ’a llist *)
val y = Node (5,Nil);
(* val y = Node (5,Nil) : int llist *)
val z = Node("A", Node("B",Nil));
(* val z = Node ("A",Node ("B",Nil)) : string llist *)
61
recursive types
datatype ’a llist = Nil | Node of ’a * (’a llist);
(* datatype ’a llist = Nil | Node of ’a * ’a llist *)
fun len Nil = 0
| len (Node (_,xs)) = 1 + len xs;
len : ’a llist -> int
len y;
(* val it = 1 : int *)
len z;
(* val it = 2 : int *)
62
recursive types
Example: Tree representation of simple mathematical expressions.
(| − 3|+ 2) + ((−1) + 4) ∗ 7)
add
abs
add mult
add2
7
neg 4neg
13
What is the datatype we need?63
recursive types
The datatype:
datatype math_tree =
Leaf of int
| Unary of (int -> int) * math_tree
| Binary of (int * int -> int) *
math_tree *
math_tree
64
recursive types
The tree in the figure:
val t =
Binary(op +,
Binary(op +,
Unary((fn x => if x > 0 then x else ~x),
Unary (op ~,
Leaf 3)),
Leaf 2),
Binary(op * ,
Binary(op +,
Unary(op ~,
Leaf 1),
Leaf 4),
Leaf 7))
65
recursive types
Evaluating the tree:
(* evaluate the math_tree *)
fun eval (Leaf n) = n
| eval (Unary (f,t)) = f (eval t)
| eval (Binary (f,l,r)) = f (eval l,eval r);
eval : math_tree -> int
- eval t;
val it = 26 : int
66
SML notes — mutual recursion
Let’s mimic the Scheme definitions of even and odd:
fun even 0 = true
| even x = odd (x - 1);
fun odd 0 = false
| odd x = even (x - 1);
Error: unbound variable or constructor: odd
67
mutual recursion
Use the keyword and.
fun even 0 = true
| even x = odd (x - 1)
and odd 0 = false
| odd x = even (x - 1);
even : int -> bool;
odd : int -> bool
even 42;
(* val it = true : bool *)
odd 42;
(* val it = false : bool *)
68
mutually recursive types
Example: a tree with labeled branches:
1
7 823
45
6
datatype ’a tree = Empty
| Node of ’a branch * ’a branch
and ’a branch = Branch of ’a * ’a tree;
69
mutually recursive types
The tree in the figure:
val lt =
Node(Branch(1,
Node(Branch(2,
Empty),
Branch(3,
Node(Branch(4,
Empty),
Branch(5,
Empty))))),
Branch(6,
Node(Branch(7,
Empty),
Branch(8,
Empty))));
70
mutually recursive types
datatype ’a tree = Empty
| Node of ’a branch * ’a branch
and ’a branch = Branch of ’a * ’a tree
Return the list of branch labels, in order:
fun listTree Empty = []
| listTree (Node (l,r)) =
(listBranch l) @ (listBranch r)
and listBranch (Branch (b,t)) =
b :: (listTree t);
listTree : ’a tree -> ’a list;
listBranch : ’a branch -> ’a list
listTree t;
(* val it = [1,2,3,4,5,6,7,8] : int list *)
71
recursive types
1. A powerful tool for constructing new types.
2. The structure of the datatype suggests the structure of therecursive function on the datatype.
72
recursive types
Recall our CFG for arithmetic expressions:
<expn> --> <expn> + <expn> |
<expn> - <expn> |
<expn> * <expn> |
<expn> / <expn> |
<identifier> |
<literal>
Let’s define a (somewhat) corresponding datatype:
datatype expn = Plus of expn * expn
| Minus of expn * expn
| Times of expn * expn
| Divn of expn * expn
| Num of real;
73
recursive types
datatype expn = Plus of expn * expn
| Minus of expn * expn
| Times of expn * expn
| Divn of expn * expn
| Num of real;
For example:
5 + 7*(-2) - 8 / 4
val e =
Minus(Plus(Num 5.0,
Times(Num 7.0,
Num ~2.0)),
Divn(Num 8.0,
Num 4.0));
74
recursive types
Evaluating the expression:
fun eval (Num n) = n
| eval (Divn (x,y)) = (eval x) / (eval y)
| eval (Times (x,y)) = (eval x) * (eval y)
| eval (Minus (x,y)) = (eval x) - (eval y)
| eval (Plus (x,y)) = (eval x) + (eval y);
eval : expn -> real;
eval e;
(* val it = ~11.0 : real *)
75
SML
• types, types, types...
• built-in types
• type synonyms, user-defined types
• enumerated, variant, union, recursive, mutually recursive
• recursion, higher-order functions (maps, folds)
76
using let in ML
fun sumcube n =
let
fun cube x = x * x * x
in
if n = 0 then 0
else cube (n) + sumcube (n-1)
end;
Is it a good idea?cube is redefined every recursive call.
77
using let in ML
Better idea:
fun sumcube n =
let
fun cube x = x * x * x;
fun sumc 0 = 0
| sumc m = cube m + sumc (m-1)
in
sumc n
end;
78
scope
Name resolution: Given the use of a name (variable or functionname), which instance of the entity with that name is referred to?
Each use of a name must be associated with a single entity atrun-time (i.e., an offset within a stack frame).
The scope of a declaration of a name is the part of the program inwhich a use of that name refers to that declaration.
The design of a language includes scope rules for resolving themapping from the use of each name to its appropriate declaration.
79
scope
A name is:
• visible to a piece of code if its scope includes that piece ofcode.
• local to a piece of code (block/ procedure/main program) ifits declaration is within that piece of code.
• non-local to a piece of code if it is visible, but its declarationis not within that piece of code.
A declaration of a name is hidden if another declaration supersedesit in scope.
80
scope example
program L;
var n: char; {n declared in L}
procedure W;
begin
write(n); {n referenced in W}
end;
procedure D;
var n: char; {n declared in D}
begin
n:= ’D’; {n referenced in D}
W
end;
begin
n:= ’L’; {n referenced in L}
W;
D
end. 81
lexical scope
• Names are associated with declarations at compile time.
• Find the smallest block syntactically enclosing the referenceand containing a declaration of the name.
• Example:• The reference to n in W is associated with the declaration of n
in L.• The output is?
• Also called static scope.
82
dynamic scope
• Names are associated with declarations at run time.
• Find the most recent, currently active run-time stack framecontaining a declaration of the name.
• Example:• The reference to n in W is associated with two different
declarations at two different times.• The output is?
83
scope
Lexical scope is based on the principle that consistent renaming ofvariables should not effect the value of an expression.
Lexical scope goes a long way to establishing referentialtransparency — easy to determine where variables obtain theirvalues, because program itself (unlike run-time contexts) is notchanging.
Most modern languages (including Scheme and ML) use lexicalscope, although early interpreted languages (LISP) used dynamicscope because of the flexibility and ease of implementation.
84
exceptions
Many functions are partial, i.e. they are only defined for a subsetof the function’s domain type. Other values in the domain typemay be treated as exceptions: e.g., f (x) = 1/x
Exceptions are a control construct. They provide a structured formof jump to exit a construct such as a function invocation or ablock.
Terminate part of computation:
• Jump out of construct.
• Pass data as part of jump.
• Return to most recent site set up to handle exception.
• Unnecessary activation records may be deallocated (may needto free heap space, other resources).
85
exceptions
Many functions are partial, i.e. they are only defined for a subsetof the function’s domain type. Other values in the domain typemay be treated as exceptions: e.g., f (x) = 1/x
Exceptions are a control construct. They provide a structured formof jump to exit a construct such as a function invocation or ablock.
Terminate part of computation:
• Jump out of construct.
• Pass data as part of jump.
• Return to most recent site set up to handle exception.
• Unnecessary activation records may be deallocated (may needto free heap space, other resources).
85
exceptions
Many functions are partial, i.e. they are only defined for a subsetof the function’s domain type. Other values in the domain typemay be treated as exceptions: e.g., f (x) = 1/x
Exceptions are a control construct. They provide a structured formof jump to exit a construct such as a function invocation or ablock.
Terminate part of computation:
• Jump out of construct.
• Pass data as part of jump.
• Return to most recent site set up to handle exception.
• Unnecessary activation records may be deallocated (may needto free heap space, other resources).
85
exceptions
Consider the following functions:
fun f x = 1.0 / x;
fun g x = hd x;
fun h x = tl x;
Will the type-checker complain? Is there a problem?
86
SML exceptions
ML’s exceptions (similar to the ones in Java or Python) provide auniform way to handle errors, and eliminate the need for ad hoc,special exceptional return values from functions.
When encountering a special case, raise an exception. The callerwill catch/handle the exception and will take care of it.
Primitive exceptions:
3 div 0 (* raises Div *)
hd nil (* raises Empty *)
87
SML user-defined exceptions
Defining an exception: exception <name> of <type>
Raising an exception: raise <name> <arg>
exception factNeg;
fun robustFact n=
let
fun fact 0 = 1
| fact n = n * fact (n-1);
in
if n < 0 then raise factNeg
else fact n
end;
robustFact 5;
(* val it = 120 : int *)
robustFact ~5;
(* uncaught exception factNeg *)88
SML exceptions
Exceptions can have arguments, just like datatypes:
exception factNeg of int;
fun robustFact n=
let
fun fact 0 = 1
| fact n = n * fact (n-1);
in
if n < 0 then raise factNeg n
else fact n
end;
89
SML exceptions
Handling exceptions:
<expr> handle <match1> => <expr1>
| <match2> => <expr2>
...
| <matchn> => <exprn>
Any restrictions on types?
90
SML exceptions
Example:
fun printFact n =
print (Int.toString(robustFact n)^"\n")
handle factNeg x =>
print ("printFact "^(Int.toString x)^
": argument must be >= 0\n");
(* val printFact = fn : int -> unit *)
printFact ~5;
(* printFact ~5: argument must be >= 0
val it = () : unit *)
91
SML exceptions
Exceptions are handled according to dynamic scoping.
exception e1 and e2 and e3;
fun h 1 = raise e1
| h 2 = raise e2
| h 3 = raise e3
| h _ = "ok";
fun g(N) = h(N)
handle e2 => "error g2"
| e3 => "error g3";
fun f(N) = g(N)
handle e1 => "error f1"
| e2 => "error f2";
f(4);
f(3);
f(2);
f(1);
f(0);
92
exception handling
General dynamic scoping rule:
• Jump to most recently established handler on run-time stack.
Dynamic scoping is not an accident:
• User knows how to handler error.
• Author of library function does not.
93
exceptions
Why do we need exceptions in a strongly typed language?
• Type-checker only checks types of parameters, not theirvalues.
• In languages w/o exception handling, when an exceptionoccurs, control goes to the OS and the program is terminated,or special code must be written to handle exceptions (e.g.,pass special parameter or use return value of procedure toindicate status of program, etc.)
• In contrast, with exception handling, programs can fix theproblem and continue, if desirable.
Some uses: (see examples in Mitchell 8.2)
• Error handling (when no reasonable value can be returned).• Efficiency (we don’t like this one).
Two types of exceptions in SML:
• Built-in exceptions.• User-defined exceptions.
94
exceptions
Why do we need exceptions in a strongly typed language?
• Type-checker only checks types of parameters, not theirvalues.
• In languages w/o exception handling, when an exceptionoccurs, control goes to the OS and the program is terminated,or special code must be written to handle exceptions (e.g.,pass special parameter or use return value of procedure toindicate status of program, etc.)
• In contrast, with exception handling, programs can fix theproblem and continue, if desirable.
Some uses: (see examples in Mitchell 8.2)
• Error handling (when no reasonable value can be returned).• Efficiency (we don’t like this one).
Two types of exceptions in SML:
• Built-in exceptions.• User-defined exceptions.
94
immutable data
• Variables (and data structures), once created and initialized,are immutable: they are never changed, updated, or storedinto.
• Powerful guarantees of noninterference.
• Build new data structures (and let the old ones be garbagecollected) instead of modifying old ones.
95
references
• functional programming ⇒ no side effects
• ML separates pure expressions and side effects.
• Assignment is restricted to reference cells, which are of adifferent type.
• Assignment restrictions are enforced by the type system.
96
references
Syntax:• ref v creates a reference cell with value v
• !r returns contents of reference cell r• r:=v sets contents of reference cell r to value v
val x = ref 0;
(* val x = ref 0 : int ref *)
x;
(* val it = ref 0 : int ref *)
!x;
(* val it = 0 : int *)
x:=3;
(* val it = () : unit *)
x;
(* val it = ref 3 : int ref *)
!x;
(* val it = 3 : int *)97
references
• The type of a reference cell that contains value v of type α isα ref.
• Assignment to a reference cell must be consistent with thetype of the reference cell
Example:
val x = ref "cscc24";
(* val x = ref "cscc24" : string ref *)
x:=0;
(* Error: operator and operand don’t agree
operator domain: string ref * string
operand: string ref * int
in expression:
x := 0
*)
98
references
(* absLst = fn : int ref list -> unit *)
fun absLst [] = ()
| absLst (x::rest) = (x:=abs(!x); absLst rest);
val L = [ref ~1, ref 0, ref ~2];
(* val L = [ref ~1,ref 0,ref ~2] : int ref list*)
absLst L;
(* val it = () : unit *)
L;
(* val it = [ref 1,ref 0,ref 2] : int ref list *)
99
references
(* absLst = fn : int list -> int list *)
fun absLst [] = []
| absLst (x::rest) = (abs x)::(absLst rest);
fun absLst lst = map abs lst;
val L = [~1, 0, ~2];
(* val L = [~1,0,~2] : int list *)
absLst L;
(* val it = [1,0,2] : int list *)
L;
(* val it = [~1,0,~2] : int list *)
100
ML
• functional
• type safe
• static type checking
• type inference
• polymorphic
• abstract data types
• exception handling
• static scope
• formal definition
• garbage collection
• immutable data types
• updatable references
101