CSC324Functional Programming, Typing
in ML
Afsaneh Fazly1
January 30, 2013
1with many thanks to Anya Tafliovich, Gerald Penn, Sheila McIlraith, WaelAboelsaddat, Tony Bonner, Eric Joanis, Suzanne Stevenson.
1
Good teaching is more a giving of the right questionsthan a giving of the right answers.
– J. Albers
2
motivation
Consider the following Scheme function definition:
(define foobar
(lambda (x)
(if (even? x)
(car x)
(cdr x))))
Anything wrong?
even? expects a number andcar, cdr expect a pair=⇒ we’ll get an error at run-time.
Can we figure this out before run-time? Can’t a programminglanguage (PL) support this kind of reasoning?
3
motivation
Consider the following Scheme function definition:
(define foobar
(lambda (x)
(if (even? x)
(car x)
(cdr x))))
Anything wrong?
even? expects a number andcar, cdr expect a pair=⇒ we’ll get an error at run-time.
Can we figure this out before run-time? Can’t a programminglanguage (PL) support this kind of reasoning?
3
motivation
Consider the following Scheme function definition:
(define foobar
(lambda (x)
(if (even? x)
(car x)
(cdr x))))
Anything wrong?
even? expects a number andcar, cdr expect a pair=⇒ we’ll get an error at run-time.
Can we figure this out before run-time? Can’t a programminglanguage (PL) support this kind of reasoning?
3
motivation
• It could be worse!
• At least Scheme checks that x is a pair before accessingmemory in the execution of (car x) and (cdr x) .
• In fact, Scheme is type safe: it will never execute (op arg)
if op is not applicable to arg .
• 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 pair before accessingmemory in the execution of (car x) and (cdr x) .
• In fact, Scheme is type safe: it will never execute (op arg)
if op is not applicable to arg .
• 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 pair before accessingmemory in the execution of (car x) and (cdr x) .
• In fact, Scheme is type safe: it will never execute (op arg)
if op is not applicable to arg .
• 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 pair before accessingmemory in the execution of (car x) and (cdr x) .
• In fact, Scheme is type safe: it will never execute (op arg)
if op is not applicable to arg .
• Languages which are not type safe (e.g., C) allow unsafememory accesses: a major source of security vulnerabilities.
4
motivation• Ideally, we would like to catch all errors at compile-time.
• Extremely difficult, if not impossible.
• 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: identify a certain class of errors and guaranteeto catch all of them.
• E.g., use type systems to catch type errors.• Guarantees range widely: from basic safety of memory accesses
to just about anything you want.
5
motivation• Ideally, we would like to catch all errors at compile-time.
• Extremely difficult, if not impossible.
• 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: identify a certain class of errors and guaranteeto catch all of them.
• E.g., use type systems to catch type errors.• Guarantees range widely: from basic safety of memory accesses
to just about anything you want.
5
motivation• Ideally, we would like to catch all errors at compile-time.
• Extremely difficult, if not impossible.
• 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: identify a certain class of errors and guaranteeto catch all of them.
• E.g., use type systems to catch type errors.• Guarantees range widely: from basic safety of memory accesses
to just about anything you want.
5
motivation• Ideally, we would like to catch all errors at compile-time.
• Extremely difficult, if not impossible.
• 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: identify a certain class of errors and guaranteeto catch all of them.
• E.g., use type systems to catch type errors.• Guarantees range widely: from basic safety of memory accesses
to just about anything you want.
5
motivation• Ideally, we would like to catch all errors at compile-time.
• Extremely difficult, if not impossible.
• 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: identify a certain class of errors and guaranteeto catch all of them.
• E.g., use type systems to catch type errors.• Guarantees range widely: from basic safety of memory accesses
to just about anything you want.
5
motivation• Ideally, we would like to catch all errors at compile-time.
• Extremely difficult, if not impossible.
• 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: identify a certain class of errors and guaranteeto catch all of them.
• E.g., use type systems to catch type errors.• Guarantees range widely: from basic safety of memory accesses
to just about anything you want.
5
motivation• Ideally, we would like to catch all errors at compile-time.
• Extremely difficult, if not impossible.
• 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: identify a certain class of errors and guaranteeto catch all of them.
• E.g., use type systems to catch type errors.• Guarantees range widely: from basic safety of memory accesses
to just about anything you want.
5
motivation• Ideally, we would like to catch all errors at compile-time.
• Extremely difficult, if not impossible.
• 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: identify a certain class of errors and guaranteeto catch all of them.
• E.g., use type systems to catch type errors.
• Guarantees range widely: from basic safety of memory accessesto just about anything you want.
5
motivation• Ideally, we would like to catch all errors at compile-time.
• Extremely difficult, if not impossible.
• 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: identify a certain class of errors and guaranteeto catch all of them.
• E.g., use type systems to catch type errors.• 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.
Note: 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.
Note: 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
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
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
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
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
typing
A programming language is type safe to the extent that it doesnot allow programs to violate its type distinctions:
• a type-safe language doesn’t allow programmers to performoperations on mixed types
• ML and Scheme are more type-safe than Python or C
The process of verifying and enforcing the constraints of types iscalled type checking, which may occur either:
• at compile-time ⇒ static type checking, or
• at run-time ⇒ dynamic type checking.
9
typing
A programming language is type safe to the extent that it doesnot allow programs to violate its type distinctions:
• a type-safe language doesn’t allow programmers to performoperations on mixed types
• ML and Scheme are more type-safe than Python or C
The process of verifying and enforcing the constraints of types iscalled type checking, which may occur either:
• at compile-time ⇒ static type checking, or
• at run-time ⇒ dynamic type checking.
9
static vs. dynamic typing
Dynamic type checking (performed at run-time):
• Slower execution: need to carry type information around, lotsof run-time checks.
• Allows for more flexible programming.
• e.g., Scheme and Python.
Static type checking (performed at compile-time):
• Faster execution: no need to carry type information; compilercan 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.
• e.g., ML and Java.
10
static vs. dynamic typing
Dynamic type checking (performed at run-time):
• Slower execution: need to carry type information around, lotsof run-time checks.
• Allows for more flexible programming.
• e.g., Scheme and Python.
Static type checking (performed at compile-time):
• Faster execution: no need to carry type information; compilercan 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.
• e.g., ML and Java.
10
static vs. dynamic typing
Dynamic type checking (performed at run-time):
• Slower execution: need to carry type information around, lotsof run-time checks.
• Allows for more flexible programming.
• e.g., Scheme and Python.
Static type checking (performed at compile-time):
• Faster execution: no need to carry type information; compilercan 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.
• e.g., ML and Java.
10
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: code does not contain explicit type annotations;types are inferred from the code.
• 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
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: code does not contain explicit type annotations;types are inferred from the code.
• 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
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: code does not contain explicit type annotations;types are inferred from the code.
• 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
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: code does not contain explicit type annotations;types are inferred from the code.
• 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
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: code does not contain explicit type annotations;types are inferred from the code.
• 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
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: code does not contain explicit type annotations;types are inferred from the code.
• 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
11
type inference
Widely regarded as an important language innovation.
ML is designed to make type inference tractable, using constraintsatisfaction techniques.
ML’s type inference algorithm [ from Mitchell 2003, pg. 136 ]:
1. assign a type to an expression and each subexpression by using theknown type of a symbol, or a type variable.
2. generate a set of constraints on types by using parse tree ofexpression.
3. solve these constraints using a substitution-based algorithm (a.k.a.unification).
12
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.
13
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.
13
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
14
ML basic types
bool: this type has two elements: true and false.
Operations on bools: not
Special operators: andalso, orelse.
For example:
- if (not true andalso false) orelse true
= then true
= else false;
val it true : bool
15
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
16
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
17
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.
18
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.
19
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.
20
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
21
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 is a type variable used to represent a type for whichequality is defined.
With reals we can use a <= b andalso b >= a. Or, muchbetter, use real arithmetic:
- Real.==(1.0, 1.0);
val it = true : bool
- Real.==(1.0, 1.00000);
val it = true : bool
22
ML basic types
string: { “Hello, world”, “CSC324 is fun!”, ... }
Operations: ^ for concatenation
For example:
- "Hello";
val it = "Hello" : string
- "Hello" ^ " " ^ "Jim";
val it = "Hello Jim" : string
23
ML basic data types: summary
• unit : the only member is ().Note: unit is basically an empty tuple.
• bool : booleans.Operations: not, andalso, orelse, ...Note: and is not a logical operator, it has a special meaningin ML.
• int : integers.Operations: all arithmetic and comparison operators.
• real : reals.Operations: all arithmetic and comparison, except = and <>.Note: use Real.== for equality.
• string : strings.Operations: ^ for concatenation
24
ML types: tuples
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
25
ML types: lists
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, ...
26
ML types: lists
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
27
ML syntax
A variable declaration in ML looks like:
• val �name� = �expr�
• val x = 42 + 24
• in Scheme: (define x (+ 42 24))
28
ML syntax
A variable declaration in ML looks like:
• val �name� = �expr�• val x = 42 + 24
• in Scheme: (define x (+ 42 24))
28
ML syntax
A variable declaration in ML looks like:
• val �name� = �expr�• val x = 42 + 24
• in Scheme: (define x (+ 42 24))
28
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�
• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�
• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�
• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.
This argumentcould be a tuple.
29
ML functions
The syntax for anonymous functions in ML is:
• fn �arg� => �body�• fn x => x + 1
• in Scheme: (lambda (x) (+ x 1))
Giving a name to a function:
• val �name� = fn �arg� => �body�• val inc = fn x => x + 1
• in Scheme: (define inc (lambda (x) (+ x 1)))
Or:
• fun �name� �arg� = �body�• fun inc x = x + 1
• in Scheme: (define (inc x) (+ x 1))
In ML every function accepts exactly one argument.This argumentcould be a tuple.
29
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
30
parametric polymorphism
What is the type of identity function fn x => x ?
- fun id x = x;
val id = fn : ??
- 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
31
parametric polymorphism
What is the type of identity function fn x => x ?
- fun id x = x;
val id = fn : ??
- 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
31
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 .
32
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 .
32
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 .
32
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 .
32
... admin notes ...
• A1 marks and reports have been emailed to your CDF account.Please send me and the TA (Jackie) an email if you have notreceived your report.
• Message from The Accessibility Services Note-TakingProgram: The Accessibility Services requires dependable volunteernote-takers to provide notes to students living with disabilities toachieve academic success.
To find out more information on how to register, please contact me,or see “Announcements” on the course webpage.
33
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;
34
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;
34
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;
34
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;
34
parametric polymorphism
What is the type of the following function?
fun len lst = if (null lst) then 0
else 1 + len (tl lst);
len : ’a list -> int
List is a polymorphic data type.
35
parametric polymorphism
What is the type of the following function?
fun len lst = if (null lst) then 0
else 1 + len (tl lst);
len : ’a list -> int
List is a polymorphic data type.
35
parametric polymorphism
What is the type of the following function?
fun len lst = if (null lst) then 0
else 1 + len (tl lst);
len : ’a list -> int
List is a polymorphic data type.
35
currying
- fun sum x y = x + y;
is short form for:
- fun sum x = (fn y => x + y)
So its type is:
val sum = fn: int -> int -> int
Note: this means int -> (int -> int), i.e. sum takes an int
and returns a function of type int -> int.
36
currying
- fun sum x y = x + y;
val sum = fn: int -> int -> int
Example calls to function sum:
- sum 2;
?
- sum 2 3;
?
37
currying
- fun sum x y = x + y;
val sum = fn: int -> int -> int
Example calls to function sum:
- sum 2;
val it = fn : int -> int
- sum 2 3;
val it = 5 : int
38
important: fn versus fun
Keywords fun and fn are both short for “function”, but they havedifferent meanings:
• fun is used for function declaration, i.e., to bind a particularidentifier to a function.
• fn is used to introduce a value that has a function type.
Remember syntax for anonymous and named functions.
39
more on currying
Compare sum1 and sum2:
- fun sum1 x y = x + y;
val sum1 = fn: int -> int -> int
- fun sum2(x,y) = x + y;
val sum2 = fn: int * int -> int
Are sum1 and sum2 different in what they do?
40
more on currying
Compare sum1 and sum2:
- fun sum1 x y = x + y;
val sum1 = fn: int -> int -> int
- fun sum2(x,y) = x + y;
val sum2 = fn: int * int -> int
Are sum1 and sum2 different in what they do? Compare:
- sum1 2;
?
- sum2 2;
?
41
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
• Curried functions allow the use of partially instantiatedfunctions, i.e., forming new functions by binding one or moreof parameters of an existing function.
42
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;
val h = 1 : int
val r = [2,3,4] : int list
43
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];
val h = 1 : int
44
pattern matching
Matching a pattern to an expression, e.g.:
val (a, f::s::r) = ("hello", [1,2,3,4]);
Parse trees for the pattern and the expression:
, ,
/ \ / \
a :: "hello" ::
/ \ / \
f :: 1 ::
/ \ / \
s r 2 ::
/ \
3 ::
/ \
4 nil
45
pattern matching
Function declaration with pattern matching:
fun <name> <pattern1> = <exp1>
| <name> <pattern2> = <exp2>
.
.
| <name> <patternN> = <expN>;
This means:
• function name is name,
• function takes one argument (like any other ML function),
• function tries to match the argument to pattern1. If itsucceeds, it returns value of exp1. Otherwise, tries to matchthe argument to pattern2, etc, etc.
46
pattern matching
Let’s define rev that takes a list and returns its reverse:
- fun rev lst =
= if null(lst) then []
= else rev(tl lst) @ [hd lst];
val rev = fn : ’a list -> ’a list
47
pattern matching
Let’s define rev that takes a list and returns its reverse:
- fun rev lst =
= if null(lst) then []
= else rev(tl lst) @ [hd lst];
val rev = fn : ’a list -> ’a list
Now we want to define rev using pattern matching:
48
pattern matching
Let’s define rev that takes a list and returns its reverse:
- fun rev lst =
= if null(lst) then []
= else rev(tl lst) @ [hd lst];
val rev = fn : ’a list -> ’a list
Now we want to define rev using pattern matching:
- fun rev nil = []
= | rev(x::xs) = rev(xs) @ [x];
val rev = fn : ’a list -> ’a list
49
pattern matching
Recall the function len that takes a list and returns its length:
fun len lst =
if (null lst) then 0
else 1 + len (tl lst);
50
pattern matching
Recall the function len that takes a list and returns its length:
fun len lst =
if (null lst) then 0
else 1 + len (tl lst);
We can (and should!) rewrite len to use pattern matching:
fun len [] = 0
| len (x::xs) = 1 + len xs;
len : ’a list -> int
51
pattern matching
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: ...
52
pattern matching
Write a function firstlist that takes a list of pairs, and returnsa new list consisting of the first elements of all pairs. 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]]
53
pattern matching
Write a function firstlist that takes a list of pairs, and returnsa new list consisting of the first elements of all pairs. 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
54
pattern matching
fun <name> <pattern1> = <exp1>
| <name> <pattern2> = <exp2>
.
.
| <name> <patternN> = <expN>;
Notes:
• All patterns must have the same type.
• All expressions must have the same type.
Why?
55
ML notes
Syntax of an ML let expression:
let
<declaration>
<declaration>
...
in
<body-expression>
end
56
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
57
Top Related