Standard ML
Declarations
Reminder: Values and Types
What’s a value?
- Integers are values
- A pair of an integer and real is a value
- Every function is a value
Reminder: Values and Types
Every value in ML has a type:
- Some types are atomic
- Some types are builtin
- Some types may be both builtin and atomic
- Other types are compound (made from smaller types)
Remember: not everything in ML is a value, types are not values!
where do values come from?
During execution, the program generates more values by using operators for computation
div
creates values of type intfn ? => ?
creates values which are functionsfun x (t) = ...
creates a value which is a fucntion and names it
A value constructor is always an operator
where do values come from?
Important Note: a type constructor is not an operator, it takes types and creates a new type
->
*
{...}
where do values come from?
Values can also be introduced by the programmer:
- Atomic values: every literal is a value
- Composite values: expressions, function definitions
declarations
- Making new values out of previous values and literals
- Providing names for this value
- Making new types out of previous types (builtin and user defined)
- Providing names for these new types
REPL
For every new value the programmer inserts, expression or declaration, the ML engine:
- Infers type of that value:
int
,int * real
,'a -> int -> int
, … - Computes the value (if it is an expression)
- Associates the name
it
with this value
Example: The Area of a Circle
\[area = \pi \cdot r^2\]val pi = 3.14159;
fun area r = pi * r * r;
area 2.0;
Identifiers in ML
val
declaration binds a name to a value- Names are not variables!
- A name can not be used to change its value (actually a constant)
- A name can be reused for another purpose
- By scoping rules
- By hiding a name in an outer scope
- By redefinitions to the REPL…
val pi = "pi";
Identifiers in ML
In case a name is declared again, the new meaning is adopted afterwards
pi;
However, this does not effect existing uses of the name
area 1.0;
is it a good feature?
val rec
We can define a function using val:
val sq = fn x => x * x;
What about recursive functions?
val f = fn n => if n = 0 then 1 else n * f (n - 1);
val rec
In order to do so, we may use val rec
:
val rec f = fn n => if n = 0 then 1 else n * f (n - 1);
NOTE:
fun
VS val rec
is a matter of taste
It is always a question when does a binding get into effect
- In Pascal and C a function ‘int f(double d) ** {…}’
-
In C (and Pascal) this applies also to types:
struct X *p; // OK Struct X is an incomplete type struct X y; // Error struct X not defined struct X { int a; }; // Complete definition of struct x. struct Y { int b; } Y; // Y is variable of type 'struct Y' typedef struct Z {int c; } Z; // Z is alias to type struct Z.
- C:
struct X *y; struct X { ....}
- gives body to type
struct X
- the type struct X exists even before the type
X
never exists
- gives body to type
- C:
Pattern Matching
Patterns can be used to simplify function definitions
fun factorial 0 = 1
| factorial n = n * factorial (n - 1);
When the function is called, the first pattern to match the argument determines which expression on the right hand side will be evaluated
Pattern Matching
Patterns can consist of:
- Constants - int, real, string, …
- Constructs - tuples, datatype constructors
- Variables - all the rest
- Underscore - a wildcard
Note: matching is recursive
Pattern Matching
What will be printed?
fun foo (x, 1) = x
| foo (1, _) = 0
| foo _ = ~1;
foo (3, 1);
foo (1, 3);
foo (2, 2);
foo (1, 1);
Patterns using case
case E of P1 => E1 | ... | Pn => En
case 7 of
0 => "zero"
| 1 => "one"
| 2 => "two"
| n => if n < 10 then "lots" else "lots &lots";
- If
Pi
is the first to match then the result isEi
- No symbol terminates the case expression - enclose in parentheses to eliminate ambiguity
Type aliasing
You can give a new name to an existing type.
type vec = real * real;
infix ++;
fun (x1, y1) ++ (x2, y2) : vec = (x1 + x2, y1 + y2);
(3.6, 0.9) ++ (0.1, 0.2) ++ (20.0, 30.0);
Note: the new name is only an alias
Declarations inside an expression
In case you want to limit a scope of a declaration, you may use a let
expression:
let D in E end
fun gcd (n, m) = if m = 0 then n else gcd (n mod m, m);
fun fraction (n, d)=
let
val com = gcd (n, d)
in
(n div com, d div com)
end;
Declarations inside an expression
D
may be a compound declaration
let
val x = 5
val y = 17
in
x * y
end;
Declarations inside an expression
let
can be simulated using anonymous functions
fun fraction (n, d) = (fn c => (n div c, d div c)) (gcd (n, d));
Nested scopes
fun sqroot a =
let
val acc = 1.0e~10
fun findroot x =
let
val nextx = (a / x + x) / 2.0
in
if abs (x - nextx) < acc * x
then nextx
else findroot nextx
end
in
findroot 1.0
end;
Declarations inside a Declaration
We may also use a local
declaration:
local D1 in D2 end
local
fun itfib (n, prev, curr): int =
if n = 1 then curr
else itfib (n - 1, curr, prev + curr)
in
fun fib n = itfib (n, 0, 1)
end;
(D1
is visible only within D2
)
Comparing let
and local
(1)
fun fib n = let
fun itfib (n, prev, curr): int =
if n = 1 then curr
else itfib (n - 1, curr, prev + curr)
in
itfib (n, 0, 1)
end;
local
fun itfib (n, prev, curr): int =
if n = 1 then curr
else itfib (n - 1, curr, prev + curr)
in
fun fib n = itfib (n, 0, 1)
end;
Comparing let
and local
(2)
fun pow4 n: int = let
val n2 = n * n
in
n2 * n2
end;
NOTE: a declaration in a let
expression may refer to a parameter of the enclosing function
Comparing let
and local
(3)
local
val x = pow4 5;
in
val y = x + 2;
val z = x * x;
end;
NOTE: the “body” of local
may contain multiple declarations
Comparing let
and local
(4)
local
val x = pow4 5
in
end;
NOTE: the “body” of local
may be empty
Simultaneous declarations (collateral)
val ID1 = E1 and ... and IDn = En
- Evaluates
E1
, …,En
- And only then declares the identifiers
ID1
, …,IDn
val x = 3;
val y = 5;
val x = y and y = x;
Mutually recursive functions
\[\frac{\pi}{4}=\sum_{k=0}^\infty \frac{1}{4k+1} - \frac{1}{4k+3} = 1 - \frac{1}{3} + \frac{1}{5} - \frac{1}{7} + \frac{1}{9} - \cdots\]fun pos d = neg (d - 2.0) + 1.0 / d
and neg d = if d > 0.0 then pos (d - 2.0) - 1.0 / d
else 0.0;
fun sum (d, one) =
if d > 0.0
then sum (d - 2.0, ~one) + one / d
else 0.0;