SML Sli
SML Sli
Imperative programming
Functional programming
Java supports object-oriented programming but methods are written in an imperative style.
int sum(int n)
{
int sum = 0;
int Double(int n)
{
return n * 2;
}
Usage:
Two uses of max can be combined to produce a function to compute the largest of three
integers:
The largest of six values can be computed by combining max and largest:
int sum(int n)
{
int sum = 0;
int sum(int n)
{
if (n == 1)
return 1;
else
return n + sum(n - 1);
}
Developed at Edinburgh University in the mid '70s by Mike Gordon, Robin Milner, and Chris
Wadsworth.
Designed specifically for writing proof strategies for the Edinburgh LCF theorem prover. A
particular goal was to have an excellent datatype system.
There is a family of languages based on ML. We’ll be using Standard ML of New Jersey
(SML/NJ).
See the class website for information on running SML/NJ on lectura and on Windows.
• Executable code is contained in functions, which may be associated with a structure but
are often “free floating”.
The OCaml (Objective Caml) language is derived from ML and has support for object-
oriented programming.
% sml
Standard ML of New Jersey, v110.57...
- 3+4;
val it = 7 : int
- 3 - 4;
val it = ~1 : int
- 10 + ~20;
val it = ~10 : int
Note the prompt is a minus sign. Use a semicolon to terminate the expression.
To use some Lisp terminology, we can say that SML/NJ provides a “read-eval-print loop”.
- 10 + ~20;
val it = ~10 : int
- it * it + it;
val it = 90 : int
-5
=*
=6+7
=;
val it = 37 : int
Note that the equal signs at the start of the lines are being printed as a secondary prompt—the
expression is recognized as being incomplete.
- real(4) ;
val it = 4.0 : real
- floor(3.45);
val it = 3 : int
- ceil(3.45);
val it = 4 : int
- size("testing");
val it = 7 : int
- Math.sqrt(2.0);
val it = 1.41421356237 : real
- Int.sign(~300);
val it = ~1 : int
- Real.min(1.0, 2.0);
val it = 1.0 : real
- Math.pi;
val it = 3.14159265359 : real
In some ways, an ML structure is similar to a Java class that has only static methods and
fields.
Functions with simple names, like ceil and size, are typically in a structure, with an alias for
the simple name. For example, real(x) is another name for Real.fromInt(x).
- radius;
val it = 2.0 : real
- area;
val it = 12.5663706144 : real
It can be said that the above adds bindings for radius and area to our environment.
- val x = 1;
val x = 1 : int
- val x = 3.4;
val x = 3.4 : real
- val x = "abc";
val x = "abc" : string
- x;
val it = "abc" : string
Technically, the environment contains three bindings for x, but only one—the last one—is
accessible.
An alphanumeric identifier begins with a letter or apostrophe and is followed by any number
of letters, apostrophes, underscores, and digits. Examples:
Examples:
The two character sets can’t be mixed. For example, <x> is not a valid identifier.
- 1 < 3;
val it = true : bool
- 1 > 3;
val it = false : bool
- 1 = 3;
val it = false : bool
- 1 <> 1+1;
val it = true : bool
- 1.0 = 1.0;
stdIn:5.1-5.10 Error: operator and operand don't agree [equality type required]
operator domain: ''Z * ''Z
operand: real * real
- 1 = 0 orelse 3 = 4;
val it = false : bool
It evaluates a boolean expression and then depending on the result, evaluates the expression
on the then “arm” or the else “arm”. The value of that expression becomes the result of the
conditional expression.
- val x = 3;
val x = 3 : int
Problem: Using nested conditional expressions, write an expression having a result of -1, 0,
or 1 depending on whether n is negative, zero, or positive, respectively.
val sign =
Note that the boolean expression is not required to be in parentheses. Instead, the keyword
then is required.
- "testing";
val it = "testing" : string
\n newline
\t tab
\\ backslash
\" double quote
\010 any character; value is in decimal (000-255)
\^A control character (must be capitalized)
Example:
- it ^ it ^ it;
val it = "Standard MLStandard MLStandard ML" : string
Based on the above, how are strings in ML different from strings in Java?
- size("abcd");
val it = 4 : int
The structures Int, Real, and Bool each have a toString function to convert values into
strings.
- Real.toString( Math.sqrt(2.0) );
val it = "1.41421356237" : string
- String.substring("0123456789", 3, 4);
val it = "3456" : string
A char literal consists of a pound sign followed by a single character, or escape sequence,
enclosed in double-quotes:
- #"a";
val it = #"a" : char
- #"\010";
val it = #"\n" : char
- String.sub("abcde", 2);
val it = #"c" : char
- chr(97);
val it = #"a" : char
- ord(#"b");
val it = 98 : int
- str(chr(97)) ^ str(chr(98));
val it = "ab" : string
What are some pros and cons of having a character type in addition to a string type?
+ - * / ~ (unary)
Boolean operators:
+-^
andalso orelse
- 3.4 + 5;
Error: operator and operand don't agree (tycon mismatch)
operator domain: real * real
operand: real * int
in expression:
+ : overloaded (3.4,5)
Example:
- fun double(n) = n * 2;
val double = fn : int -> int
The body of a function is a single expression. The return value of the function is the value of
that expression. There is no “return” statement.
The text "fn" is used to indicate that the value defined is a function, but the function itself is
not displayed.
The text "int -> int" indicates that the function takes an integer argument and produces an
integer result. ("->" is read as "to".)
Note that the type of the argument and the type produced by the function are not specified.
Instead, type deduction was used.
- fun double(n) = n * 2;
val double = fn : int -> int
- double(double(3));
val it = 12 : int
- double;
val it = fn : int -> int
- val f = double;
val f = fn : int -> int
- f(5);
val it = 10 : int
- fun f(a, b, c, d) =
if a = b then c + 1 else
if a > b then c else b + d;
val f = fn : int * int * int * int -> int
The symbols * and -> are both type operators. * is left-associative and has higher
precedence than ->, which is right-associative.
- max3(~1, 7, 2);
val it = 7 : int
Problem: Write a function sum(N) that returns the sum of the integers from 1 through N.
Assume N is greater than zero.
Problem: Write a function countTo(N) that returns a string like this: “1...2...3”, if N is 3, for
example. Use Int.toString to convert int values to strings.
Imagine we want a function to square real numbers. The obvious definition for a square
function produces the type int -> int:
- fun square(x) = x * x;
val square = fn : int -> int
- fun real(x:real) = x * x;
val real = fn : real -> real
A type variable expresses type equivalences among parameters and between parameters and
the return value.
- fun f(a) = a;
val f = fn : 'a -> 'a
The identifier 'a is a type variable. The type of the function indicates that it takes a
parameter of any type and returns a value of that same type, whatever it is.
- f(1);
val it = 1 : int
- f(1.0);
val it = 1.0 : real
- f("x");
val it = "x" : string
- fun f(a) = a;
val f = fn : 'a -> 'a
The function f is said to be polymorphic because it can operate on a value of any type.
- fun third(x, y, z) = z;
val third = fn : 'a * 'b * 'c -> 'c
- third(1, 2, 3);
val it = 3 : int
- fun equal(a,b) = a = b;
val equal = fn : ''a * ''a -> bool
The function equal can be called with any type that can be tested for equality. ''a is an
equality type variable, distinguished by the presence of two apostrophes, instead of just one.
- equal(1,10);
val it = false : bool
Another example:
% cat funcs.sml
fun double(n) = n * 2
% sml
- use("funcs.sml");
[opening funcs.sml]
val double = fn : int -> int
val bracket = fn : string -> string
- double(3);
val it = 6 : int
- bracket("abc");
val it = "{abc}" : string
- ^D
% cat test.sml
use("funcs.sml");
(* Test cases *)
val r1 = double(3);
val r2 = bracket("abc");
val r3 = bracket(bracket(""));
% sml
- use("test.sml");
[opening test.sml]
[opening funcs.sml]
val double = fn : int -> int
val bracket = fn : string -> string
val r1 = 6 : int
val r2 = "{abc}" : string
val r3 = "{{}}" : string
Note the use of r1, r2, ... to associate results with test cases.
- run();
[opening test.sml]
[opening funcs.sml]
val double = fn : int -> int
val bracket = fn : string -> string
val r1 = 6 : int
val r2 = "{abc}" : string
val r3 = "{{}}" : string
...repeat...
On lectura:
On both Windows and lectura, sml exits after processing redirected input.
use is such a function. Note its type, and the result of a call:
- use;
val it = fn : string -> unit
- use("x.sml");
[opening x.sml]
...Output from evaluating expressions in x.sml...
val it = () : unit
There is only one value having the type unit. That value is represented as a pair of
parentheses.
- ();
val it = () : unit
exception BadArg;
fun sum(N) = if N < 1 then raise BadArg
else if N = 1 then 1
else N + sum(N-1);
Usage:
- sum(10);
val it = 55 : int
- sum(~10);
uncaught exception BadArg
raised at: big.sml:26.35-26.41
- (1, 1);
val it = (1,1) : int * int
- (it, it);
val it = ((1,1),(1,1)) : (int * int) * (int * int)
string * int
(((1)))
- pair(1, "one");
val it = (1,"one") : int * string
- pair(it, it);
val it = ((1,"one"),(1,"one")): (int * string) * (int * string)
- pair((c,i,c), (1,1,(c,c)));
val it = (("a",1,"a"),(1,1,("a","a"))) : (string * int * string) * (int * int * (string * string))
- order(3,4);
val it = (3,4) : int * int
- order(10,1);
val it = (1,10) : int * int
Problem: Write a function rev3 that reverses the sequence of values in a 3-tuple. What is its
type?
Recall order:
The pattern specification (x, y) indicates that the name x is bound to the first value of the
two-tuple that order is called with. The name y is bound to the second value.
Consider this:
- order(x);
val it = (3,7) : int * int
- swap(x);
val it = (3,7) : int * int
- swap(order(x));
val it = (7,3) : int * int
Problem: Write a function descOrder that orders an int * int in descending order.
function value
In other words, two values side by side are considered to be a function call.
Examples:
- val x = (7,3);
val x = (7,3) : int * int
- swap x;
val it = (3,7) : int * int
- size "testing";
val it = 7 : int
- 1 2;
stdIn:15.1-15.4 Error: operator is not a function [literal]
operator: int
in expression:
12
- swap order x;
stdIn:65.1-65.13 Error: operator and operand don't agree [tycon mismatch]
operator domain: 'Z * 'Y
operand: int * int -> int * int
in expression:
swap order
- lowerRight m;
val it = 4 : int
- fun five() = 5;
val five = fn : unit -> int
- five ();
val it = 5 : int
In this case, the pattern is the literal for unit. Alternatively, we can bind a name to the value
of unit and use that name:
- val x = ();
val x = () : unit
- five x;
val it = 5 : int
Is five(x) valid?
- fun f(1) = 10
| f(2) = 20
| f(n) = n;
val f = fn : int -> int
Usage:
- f(1);
val it = 10 : int
- f(2);
val it = 20 : int
- f(3);
val it = 3 : int
A better way:
fun sum(0) = 0
| sum(n) = n + sum(n - 1);
- fun f(1) = 10
| f(2) = 20;
stdIn:10.5-11.14 Warning: match nonexhaustive
1 => ...
2 => ...
This warning indicates that there is at least one value of the appropriate type (int, here) that
isn’t matched by any of the cases.
- f(3);
uncaught exception nonexhaustive match failure
- [1, 2, 3];
val it = [1,2,3] : int list
- [it, it];
val it = [["just","a","test"],["just","a","test"]] : string list list
Note the type, int list, for example. list is another type operator. It has higher precedence
than both * and ->.
- [ ];
val it = [] : 'a list
- nil;
val it = [] : 'a list
Recall that all elements of a list must be of the same type. Which of the following are valid?
[ [ ],[1, 2]];
[ [ ], [ [ ] ] ];
- hd x;
val it = 1 : int
- tl x;
val it = [2,3,4] : int list
- tl it;
val it = [3,4] : int list
Both hd and tl, as all functions in our ML world, are applicative. They produce a value but
don’t change their argument.
Given val x = [1, 2, 3, 4], what is the value of hd(tl x))? How about hd( hd (tl x))?
- [1,2,3] = [1,2,3];
val it = true : bool
- [ ] = [ ];
stdIn:27.4 Warning: calling polyEqual
val it = true : bool
• Ponder this: Just as you cannot change an integer's value, you cannot change the value
of a string, list, or tuple.
1
Unless we use the imperative features of ML, which we won’t be studying!
- length [ ];
val it = 0 : int
Problem: Write a function len that behaves like length. What type will it have?
Problem: Write a function sum that calculates the sum of the integers in a list:
- sum([1,2,3,4]);
val it = 10 : int
- 1::[2];
val it = [1,2] : int list
- 1::2::3::[ ];
val it = [1,2,3] : int list
- 1::[ ];
val it = [1] : int list
- "x"::nil;
val it = ["x"] : string list
1::2::3::nil
is equivalent to
1::(2::(3::nil))
(1,2)::nil
[1]::[[2]]
nil::nil
nil::nil::nil
length::nil
- [1, "x"];
Error: operator and operand don't agree [literal]
operator domain: int * int list
operand: int * string list
in expression:
1 :: "x" :: nil
The [...] form is syntactic sugar—we can live without it, and use only cons, but it sweetens
the language a bit.
- m_to_n(1, 5);
val it = [1,2,3,4,5] : int list
- m_to_n(~3, 3);
val it = [~3,~2,~1,0,1,2,3] : int list
- m_to_n(1, 0);
val it = [ ] : int list
public List(Object head, List tail) { Here is a simple model of ML lists in Java.
this.head = head; this.tail = tail;
}
- [1,2] @ [3,4];
val it = [1,2,3,4] : int list
- it @ it @ it;
val it = [1,2,3,4,1,2,3,4,1,2,3,4] : int list
- op@ ;
val it = fn : 'a list * 'a list -> 'a list
Problem: Write a function iota(n) that produces a list of the integers between 1 and n
inclusive. Don't use m_to_n. Example:
- iota(5);
val it = [1,2,3,4,5] : int list
- explode("boom");
val it = [#"b", #"o", #"o", #"m"] : char list
- explode("");
val it = [ ] : char list
- implode([ ]);
val it = "" : string
Problem: Write a function reverse(s), which reverses the string s. Hint: rev reverses a list.
- concat([ ]);
val it = "" : string
fun len ([ ]) = 0
| len (x::xs) = 1 + len(xs)
The first pattern is the basis case and matches an empty list.
The second pattern requires a list with at least one element. The head is bound to x and the
tail is bound to xs.
Problem: Write a function sum_evens(L) that returns the sum of the even values in L, an int
list.
Problem: Write a function drop2(L) that returns a copy of L with the first two values
removed. If the length of L is less than 2, return L.
Problem: Write a function contains(s, c) that produces true iff the char c appears in the
string s.
Problem: Write a function maxint(L) that produces the largest integer in the list L. Raise the
exception Empty if the list has no elements.
• An identifier
• An underscore
val [ [ (x, y) ] ] = [ [ ( 1, 2) ] ];
val [ [ x, y ] ] = [ [ 1, 2, 3 ] ];
fun calc(x,y,z) =
let
val diff = g(x+y) - h(z)
in
f1(diff) + f2(diff)
end
Would it be practical for a compiler to make the above transformation automatically, using
CSE (common subexpression elimination)?
let
declaration1
declaration2
...
declarationN
in
expression
end
The value of expression is the value produced by the overall let expression. The
name/value binding(s) established in the declaration(s) are only accessible in expression.
- x;
stdIn:2.1 Error: unbound variable or constructor: x
fun hundredthPower(x:real) =
let
val four = x*x*x*x
val twenty = four*four*four*four*four
in
twenty*twenty*twenty*twenty*twenty
end
Usage:
- hundredthPower(10.0);
val it = 1.0E100 : real
Usage:
- count_eo([7,3,5,2]);
val it = (1,3) : int * int
- count_eo([2,4,6,8]);
val it = (4,0) : int * int
- remove_min([3,1,4,2]);
val it = (1,[3,2,4]) : int * int list
- remove_min([3,2,4]);
val it = (2,[3,4]) : int * int list
- remove_min([3,4]);
val it = (3,[4]) : int * int list
- remove_min([4]);
val it = (4,[ ]) : int * int list
fun remsort([ ]) = []
| remsort(L) =
let
val (min, remain) = remove_min(L)
in
min::remsort(remain)
end
Usage:
- remsort([3,1,4,2]);
val it = [1,2,3,4] : int list
- every_nth([10,20,30,40,50,60,70], 3);
val it = [30,60] : int list
Implementation:
fun every_nth(L, n) =
let
fun select_nth([ ],_,_) = [ ]
| select_nth(x::xs, elem_num, n) =
if elem_num mod n = 0 then
x::select_nth(xs, elem_num+1, n)
else
select_nth(xs, elem_num+1, n)
in
select_nth(L, 1, n)
end;
- print("abc");
abcval it = () : unit
fun printN(n) =
let
fun printN'(0) = ""
| printN'(n) = printN'(n - 1) ^ Int.toString(n) ^ "\n"
in
print(printN'(n))
end
Note the similarity between this function and countTo, on slide 37 (1...2...3). Could a
generalization provide both behaviors?
- use "errors.sml";
[opening errors.sml]
errors.sml:4.5 Error: syntax error: inserting VAL
errors.sml:6.10-6.13 Error: unbound variable or constructor: mud
errors.sml:7.31-7.34 Error: unbound variable or constructor: Odd
fun sum(0) = 0
| sum(n) = n + sum(n);
Usage:
- sum(5);
...no response...
^C
Interrupt
- len([1,2,3]);
uncaught exception nonexhaustive match failure
raised at: stdIn:368.3
- expand("x3y4z");
val it = "xyyyzzzz" : string
- expand("123456");
val it = "244466666" : string
Fact: The digits 0 through 9 have the ASCII codes 48 through 57. A character can be
converted to an integer by subtracting from it the ASCII code for 0. Therefore,
- ctoi(#"5");
val it = 5 : int
- is_digit(#"x");
val it = false : bool
fun repl(x, 0) = []
| repl(x, n) = x::repl(x, n-1)
Finally, expand:
fun expand(s) =
let
fun expand'([ ]) = [ ]
| expand'([c]) = [c]
| expand'(c1::c2::cs) =
if is_digit(c1) then
repl(c2, ctoi(c1)) @ expand'(cs)
else
c1 :: expand'(c2::cs)
in
implode(expand'(explode(s)))
end;
In this problem we will consider a function travel of type string -> string that moves the
robot about the grid and determines if the robot ends up where it started (i.e., did it get
home?) or elsewhere (did it get lost?).
1
2
If the robot starts in square R the command string nnnn leaves the robot in the square marked
1. The string nenene leaves the robot in the square marked 2. nnessw and news move the
robot in a round-trip that returns it to square R.
- travel("nnnn");
val it = "Got lost" : string
- travel("nnessw");
val it = "Got home" : string
Example:
Another:
fun travel(s) =
let
fun mk_tuples([ ]) = [ ]
| mk_tuples(c::cs) = mapmove(c)::mk_tuples(cs)
in
if disp = (0,0) then
"Got home"
else
"Got lost"
end
Note that mapmove and sum_tuples are defined at the outermost level. mk_tuples is
defined inside a let. Why?
(*
* inc_entry(c, L)
*
* L is a list of (char * int) tuples that indicate how many times a
* character has been seen.
*
* inc_entry() produces a copy of L with the count in the tuple
* containing the character c incremented by one. If no tuple with
* c exists, one is created with a count of 1.
*)
fun inc_entry(c, [ ]) = [(c, 1)]
| inc_entry(c, (char, count)::entries) =
if c = char then
(char, count+1)::entries
else
(char, count)::inc_entry(c, entries)
fun mkentries(s) =
let
fun mkentries'([ ], entries) = entries
| mkentries'(c::cs, entries) =
mkentries'(cs, inc_entry(c, entries))
in
mkentries'(explode s, [ ])
end
fun sort([ ]) = [ ]
| sort(x::xs) = insert(x, sort(xs))
(*
* tally: make entries, sort the entries, and print the entries
*)
fun tally(s) = print(fmt_entries(sort(mkentries(s))))
In essence, the fun declaration creates a function value and binds it to a name. Additional
names can be bound to a function value with a val declaration.
- twice;
val it = fn : int -> int
- twice 3;
val it = 6 : int
Note that unlike values of other types, no representation of a function is shown. Instead, "fn"
is displayed. (Think flexibly: What could be shown instead of only fn?)
- hd convs;
val it = fn : real -> int
- it 4.3;
val it = 4 : int
- [it];
val it = [(fn,1,fn,"x",fn)]
: (('a list -> 'a) * int * (string -> int) * string * ('b list -> int)) list
- swap(pair(op~, op^));
val it = (fn,fn) : (string * string -> string) * (int -> int)
What are some other languages that allow functions to be treated as values, at least to some
extent?
Usage:
- apply(size, "abcd");
val it = 4 : int
- apply(swap, (3,4));
val it = (4,3) : int * int
fun applyToAll(_, [ ]) = [ ]
| applyToAll(f, x::xs) = f(x)::applyToAll(f, xs);
Usage:
- applyToAll(real, iota(5));
val it = [1.0,2.0,3.0,4.0,5.0] : real list
- applyToAll(implode,
applyToAll(rev,
applyToAll(explode, ["one", "two", "three"])));
val it = ["eno","owt","eerht"] : string list
- fun one _ = 1;
val one = fn : 'a -> int
- sumInts(applyToAll(one, L));
val it = 7 : int
Problem: Create a list like ["x", "xx", "xxx", ... "xxxxxxxxxx"]. (One to ten "x"s.)
We'll see later that applyToAll is really the map function from the library, albeit in a slightly
different form.
- g(5);
val it = 25 : int
- h([10,20,30]);
val it = 3 : int
fun sort([ ]) = [ ]
| sort(x::xs) = insert(x, sort(xs))
Consider eliminating ordered_pair and instead supplying a function to test whether the
values in a 2-tuple are the desired order.
Types:
- insert;
val it = fn : 'a * 'a list * ('a * 'a -> bool) -> 'a list
- sort;
val it = fn : 'a list * ('a * 'a -> bool) -> 'a list
- sort([4,10,7,3], intLessThan);
val it = [3,4,7,10] : int list
We might sort (int * int) tuples based on the sum of the two values:
Problem: Sort an int list list based on the largest value in each of the int lists. Sorting
[[3,1,2],[50],[10,20],[4,3,2,1]]
would yield
[[3,1,2],[4,3,2,1],[10,20],[50]]
- add 3 5;
val it = 8 : int
Note the type of add: int -> (int -> int) (Remember that -> is right-associative.)
- (add 3) 5;
val it = 8 : int
add is a function that takes an int and produces a function that takes an int and produces an
int. add 3 produces a function that is then called with the argument 5.
Is add(3,5) valid?
- add 3;
val it = fn : int -> int
The name plusThree is bound to a function that is a partial instantiation of add. (a.k.a.
partial application)
- plusThree 5;
val it = 8 : int
- plusThree 20;
val it = 23 : int
fun add x y = x + y
fun plusThree(y) = 3 + y
The idea of a partially applicable function was first described by Moses Schönfinkel. It was
further developed by Haskell B. Curry. Both worked wtih David Hilbert in the 1920s.
- fun add x y = x + y;
val add = fn : int -> int -> int
Analogy: A partially instantiated function is like a machine with a hardwired input value.
- val f_a = f 1;
val f_a = fn : int -> int -> int
- f_a_b 3;
val it = 20 : int
- f_a 5 10;
val it = 57 : int
f 10 20 30;
In C, it's said that "declaration mimics use"—a declaration like int f() means that if you see
the expression f(), it is an int. We see something similar with ML function declarations:
Usage:
- m_to_n 1 7;
val it = [1,2,3,4,5,6,7] : int list
- val L = m_to_n ~5 5;
val L = [~5,~4,~3,~2,~1,0,1,2,3,4,5] : int list
Problem: Create the function iota, described on slide 78. (iota(3) produces [1,2,3].)
- fun add x y = x + y;
val add = fn : int -> int -> int
fun add x y z = x + y + z;
val x = add 1;
val xy = x 2;
xy 3;
xy 10;
x 0 0;
fun sort _ [ ] = [ ]
| sort isInOrder (x::xs) = insert(x, (sort isInOrder xs), isInOrder)
Usage:
- int_sort [4,2,1,8];
val it = [1,2,4,8] : int list
Why does the curried form have the function as the first argument?
String.isSubstring returns true iff its first argument is a substring of the second argument:
- String.isSubstring;
val it = fn : string -> string -> bool
We can create a partial application that returns true iff a string contains "tan":
- hasTan "standard";
val it = true : bool
- hasTan "library";
val it = false : bool
See the Resources page on the website for a link to documentation for the Basis.
- fun add x =
let
fun add' y = x + y
in
add'
end
val add = fn : int -> int -> int (Remember associativity: int -> (int -> int) )
A call such as add 3 produces an instance of add' where x is bound to 3. That instance is
returned as the value of the let expression.
- add 3;
val it = fn : int -> int
- it 4;
val it = 7 : int
- add 3 4;
val it = 7 : int
- map;
val it = fn : ('a -> 'b) -> 'a list -> 'b list
There is no reason to write a function that performs an operation on each value in a list.
Instead create a function to perform the operation on a single value and then map that
function onto lists of interest.
- applyToAll;
val it = fn : ('a -> 'b) * 'a list -> 'b list
- map;
val it = fn : ('a -> 'b) -> 'a list -> 'b list
(Note that the full value is not shown—the trailing # indicates the value was truncated for
display.)
Problem: Write a function equalsIgnoreCase of type string * string -> bool. The function
Char.toLower (char -> char) converts upper-case letters to lower case and leaves other
characters unchanged.
The partial application "plugs in" one of the addends. The resulting function is then called
with each value in the list in turn serving as the other addend.
- addTenToAll [3,1,4,5];
val it = [13,11,14,15] : int list
The expression being evaluated, fn(n) => n * 2, is a simple example of a match expression.
It provides a way to create a function "on the spot".
If we want to triple the numbers in a list, instead of writing a triple function we might do this:
The function created by fn(n) => n * 3 never has a name. It is an anonymous function. It is
created, used, and discarded.
The term match expression is ML-specific. A more general term for an expression that
defines a nameless function is a lambda expression.
- fun filter F [ ] = [ ]
| filter F (x::xs) = if (F x) then x::(filter F xs)
else (filter F xs);
val filter = fn : ('a -> bool) -> 'a list -> 'a list
- f [5,10,12,21,32];
val it = [10,12,32] : int list
- List.partition;
val it = fn : ('a -> bool) -> 'a list -> 'a list * 'a list
- Char.isPunct;
val it = fn : char -> bool
- grep;
val it = fn : string -> string list -> unit list
We could use SMLofNJ.exportFn to create a file that is executable from the UNIX
command line, just like the real grep.
Notes:
• TextIO.openIn opens a file for reading.
• TextIO.input reads an entire file and returns it as a string.
• Study the use of anonymous functions, mapping, and partial application.
• No loops, no variables, no recursion at this level.
How much code would this be in Java? Do you feel confident the code above is correct?
Usage:
What happens:
Or,
op+(3, op+(4, op+(5,6)))
- sum(iota 10);
val it = 55 : int
- max [5,3,9,1,2];
val it = 9 : int
- foldl;
val it = fn : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b
- foldr;
val it = fn : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b
- reduce;
val it = fn : ('a * 'a -> 'a) -> 'a list -> 'a
Our reduce has two weaknesses: (1) It can't operate on an empty list. (2) The operation must
produce the same type as the list elements.
Note that the empty list in op::(4, [ ]) comes from the call. (Try it with [10] instead of [ ].)
foldl (note the "L") folds from the left, not the right:
What characteristic of an operation leads to different results with foldl and foldr?
fun travel(s) =
let
val tuples = map dirToTuple (explode s)
val displacement = foldr addTuples (0,0) tuples
in
if displacement = (0,0) then "Got home"
else "Got lost"
end
Usage:
- length;
val it = fn : 'a list -> int
- explode;
val it = fn : string -> char list
Could we create a function composeAll([f1, f2, ... fn], x) that would call f1(f2(...fn(x)))?
- op o; (* lower-case "Oh" *)
val it = fn : ('a -> 'b) * ('c -> 'a) -> 'c -> 'b
- strlen "abc";
val it = 3 : int
(('a list -> int) * (string -> 'a list)) -> (string -> int)
Note that the following three compositions all have the same type. (Yes, the latter two are
doing some "busywork"!)
- length o explode;
val it = fn : string -> int
A COMMON ERROR is to say the type of length o explode is something like this:
Assuming a composition is valid, the type is based only on the input of the rightmost function
and the output of the leftmost function.
Because these machine models assume left to right data flow, explode comes first.
- descOrder(1,4);
val it = (4,1) : int * int
Problem: Create a function to reverse each string in a list of strings and reverse the order of
strings in the list. (Example: f ["one","two","three"] would produce ["eerht","owt","eno"].)
- second([4,2,7,5]);
val it = 2 : int
- third([4,2,7,5]);
val it = 7 : int
- xrepl(1, 5);
val it = [1,1,1,1,1] : int list
Create a function repl(s, n), of type string * int -> string, that produces a string consisting of
n copies of s. For example, repl("abc", 2) = "abcabc".
Problem: Compute the sum of the odd numbers between 1 and 100, inclusive. Use only
composition and applications of op+, iota, isEven, foldr, filter, and not (bool -> bool).
- q("x");
val it = "f(g(g(h(f(x)))))" : string
- fun compN f 1 = f
| compN f n = f o compN f (n-1);
val compN = fn : ('a -> 'a) -> int -> 'a -> 'a
Usage:
- f 10;
val it = 80 : int
- compN double 10 1;
val it = 1024 : int
- fun c f x y = f (x,y);
val c = fn : ('a * 'b -> 'c) -> 'a -> 'b -> 'c
Usage:
- c op+ 3 4;
val it = 7 : int
What is it doing?
c op+
c op^
Consider:
- op+;
val it = fn : int * int -> int
Bottom line:
If we have a function that takes a 2-tuple, we can easily produce a curried version of the
function.
- repl("abc", 4);
val it = "abcabcabcabc" : string
- stars 10;
val it = "**********" : string
- arrows 5;
val it = " ---> ---> ---> ---> ---> " : string
- fun f x y = g(x,y*2);
val f = fn : int -> int -> int
Imagine that we'd like to map f onto an (int * int) list. We can't! (Why?)
Important: The key to understanding functions like curry and uncurry is that without
partial application they wouldn't be of any use.
Example:
- five "*";
val it = "*****" : string
- five "<x>";
val it = "<x><x><x><x><x>" : string
- fun swapArgs f x y = f y x;
val swapArgs = fn : ('a -> 'b -> 'c) -> 'b -> 'a -> 'c
Usage:
- f "a" "b";
val it = "ba" : string
- five "*";
val it = "*****" : string
- five "<->";
val it = "<-><-><-><-><->" : string
Or,
- val five = swapArgs (curry repl) 5;
val five = fn : string -> string
- five "xyz";
val it = "xyzxyzxyzxyzxyz" : string
Example:
- optab;
val it = fn : (int * int -> int) * int * int -> unit
- optab(op*, 5, 7);
1 2 3 4 5 6 7
1 1 2 3 4 5 6 7
2 2 4 6 8 10 12 14
3 3 6 9 12 15 18 21
4 4 8 12 16 20 24 28
5 5 10 15 20 25 30 35
val it = () : unit
- datatype Shape =
Circle of real
| Square of real
| Rectangle of real * real
| Point;
datatype Shape
= Circle of real | Point | Rectangle of real * real | Square of real
This defines a new type named Shape. An instance of a Shape is a value in one of four
forms:
A Point, which has no data associated with it. (Debatable, but good for an example.)
datatype Shape =
Circle of real
| Square of real
| Rectangle of real * real
| Point
This declaration defines four constructors. Each constructor specifies one way that a Shape
can be created.
- val p = Point;
val p = Point : Shape
Usage:
- val r = Rectangle(3.4,4.5);
val r = Rectangle (3.4,4.5) : Shape
- area(r);
val it = 15.3 : real
- area(Circle 1.0);
val it = 3.14159265359 : real
Speculate: What will happen if the case for Point is omitted from area?
datatype Expression =
Let of (Name * int) list * Expression
| E of Expression * ArithOp * Expression
| Seq of Expression list
| Con of int
| Var of Name;
The Let expression allows integer values to be bound to names. The pseudo-code
- eval(Let([("a",10),("b",20),("c",30)],
E(Var "a", Plus, E(Var "b", Times, Var "c"))));
val it = 610 : int
let a = 1, b = 2
in a + ((let b = 3 in b*3) + b)
- eval(Let([("a",1),("b",2)],
E(Var "a", Plus,
E(Let([("b",3)], (* this binding overrides the first binding of "b" *)
E(Var "b", Times, Con 3)), Plus, Var "b"))));
val it = 12 : int
The Seq expression allows sequencing of expressions and produces the result of the last
expression in the sequence:
fun eval(e) =
let
fun eval'(Con i, _) = i
| eval'(E(e1, Plus, e2), bs) = eval'(e1, bs) + eval'(e2, bs)
| eval'(E(e1, Minus, e2), bs) = eval'(e1, bs) - eval'(e2, bs)
| eval'(E(e1, Times, e2), bs) = eval'(e1, bs) * eval'(e2, bs)
| eval'(E(e1,Divide,e2), bs) = eval'(e1, bs) div eval'(e2,bs)
| eval'(Var v, bs) = lookup(v, bs)
| eval'(Let(nbs, e), bs) = eval'(e, nbs @ bs)
| eval'(Seq([ ]), bs) = 0
| eval'(Seq([e]), bs) = eval'(e, bs)
| eval'(Seq(e::es), bs) = (eval'(e,bs); eval'(Seq(es),bs))
in
eval'(e, [ ])
end;
Some functional languages, like Haskell, use lazy evaluation—values are not computed until
needed. In Haskell the infinite list 1, 3, 5, ... can be created like this: [1,3 .. ].
% hugs
Hugs> head [1,3 ..]
1
Note that 'a is used to specify that values of any (one) type can be held in the list.
Similarly, we provide head and tail functions that mimic hd and tl but operate on a Cons.
1
Adapted from ML for the Working Programmer L.C. Paulson
fun head(Cons(x,_)) = x;
fun tail(Cons(_,f)) = f();
- byTen 100;
val it = Cons (100,fn) : int InfList
- tail it;
val it = Cons (110,fn) : int InfList
- tail it;
val it = Cons (120,fn) : int InfList
Try it!
- toggle "on";
val it = Cons ("on",fn) : string InfList
- tail it;
val it = Cons ("off",fn) : string InfList
- tail it;
val it = Cons ("on",fn) : string InfList
- tail it;
val it = Cons ("off",fn) : string InfList
- repeatValues;
val it = fn : 'a list -> 'a InfList
- tail it;
val it = Cons (#"d",fn) : char InfList
- tail it;
val it = Cons (#"q",fn) : char InfList
- tail it;
val it = Cons (#"p",fn) : char InfList
- tail it;
val it = Cons (#"d",fn) : char InfList