Standard ML

236319 Spring 2024, Prof. Lorenz


Standard ML

Lists


Lists

Lists are immutable finite sequences of elements

[3, 5, 9]: int list
["a", "list"]: str list
[]: 'a list

Lists

Order matters

[1, 2, 3] <> [3, 2, 1];

And repetitions count

[3, 3, 3] <> [3];

Lists

Elements may have any type

[(1, "One"), (2, "Two")] : (int*string) list
[[3.1], [], [5.7, ~0.6]]: real list list

… but all elements must have the same type

[5, "five"]; (*ERROR*)

Lists

The empty list has a polymorphic type

[]: 'a list

nil is a synonym of []

nil;

Building lists

A list is either empty or a head followed by a tail

[1,2,3] ➭ head: 1 tail: [2,3]

building lists

Use the infix operator :: (aka cons) to build a list

1 :: [2, 3];

1 :: 2 :: 3 :: [];

building lists

:: associates to the right, so

x1 :: x2 :: ... :: xn :: nil

=

(x1 :: (x2 :: (... :: (xn :: nil)...)

building lists

:: is a constructor so it can be used in patterns

fun replace_head (_ :: t) x = x :: t
  | replace_head [] _ = [];

builtin fundamental functions

builtin fundamental functions

null - tests whether a list is empty

fun null [] = true
  | null (_ :: _) = false;

builtin fundamental functions

hd - evaluates to the head of a non-empty list

fun hd (x :: _) = x;

builtin fundamental functions

hd [ [ [1,2], [3] ], [ [4] ] ];
hd it;
hd it;

builtin fundamental functions

tl - evaluates to the tail of a non-empty list

fun tl (_ :: xs) = xs;

builtin fundamental functions

tl ["how", "are", "you?"];
tl it;
tl it;
tl it;

example - building a list of integers

fun range (m, n) =
  if m = n then []
  else m :: (range (m + 1, n));

range (2, 5);

example - building a list of integers

infix --;
val op-- = range;

2 -- 5;

take and drop

\(xs = [x_1, x_2, x_3, \ldots, x_k, x_{k+1}, \ldots, x_n]\) \(take (k, xs) = [x_1, x_2, x_3, \ldots, x_k]\) \(drop (k, xs) = [x_{k+1}, \ldots, x_n]\)

the computation of take

fun take (0, _)     = []
  | take (i, x :: xs) = x :: (take (i - 1, xs));
take (3, [9, 8, 7, 6, 5, 4])
9 :: take (2, [8, 7, 6, 5, 4])
9 :: (8 :: take (1, [7, 6, 5, 4]))
9 :: (8 :: (7 :: take (0, [6, 5, 4])))
9 :: (8 :: (7 :: []))
9 :: (8 :: [7])
9 :: [8,7]
[9, 8, 7]

the computation of drop

fun drop (0, xs)    = xs
  | drop (i, _ :: xs) = drop (i - 1, xs);
drop (3, [9, 8, 7, 6, 5, 4])
drop (2,   [8, 7, 6, 5, 4])
drop (1,     [7, 6, 5, 4])
drop (0,       [6, 5, 4])
[6, 5, 4]

tail recursion

recursion types

normal recursion

fun take (0, _)     = []
  | take (i, x :: xs) = x :: (take (i - 1, xs));

tail recursion

fun drop (0, xs)    = xs
  | drop (i, _ :: xs) = drop (i - 1, xs);

normal to tail recursive

fun length []      = 0
  | length (_ :: xs) = 1 + length xs;

Use an accumulator to make it iterative

local
  fun ilen (n, [])    = n
    | ilen (n, _ :: xs) = ilen (n + 1, xs)
in
  fun length xs = ilen (0, xs)
end;

builtin append operator

[x1,...,xm] @ [y1,...,yn] = [x1,...,xm,y1,...,yn]

infix @;
fun []      @ ys = ys
  | (x :: xs) @ ys = x :: (xs @ ys);

["Append", "is"] @ ["never", "boring"];
  • Is it tail recursive?
  • Why can’t it be used in patterns?

side note - orelse and andalso

they are short-circuiting boolean operators

B1 andalso B2 = if B1 then B2  else false;

B1 orelse  B2 = if B1 then true else B2;

orelse and andalso

fun even n = (n mod 2 = 0);

fun powoftwo n =
  (n = 1) orelse
  (even n andalso powoftwo (n div 2));

Is powoftwo tail-recursive?


builtin function map

fun map f []      = []
  | map f (x :: xs) = (f x) :: (map f xs);
val sqlist = map (fn x => x * x);

sqlist [1, 2, 3];

builtin function map

Transposing a matrix using map:

fun transp ([] :: _) = []
  | transp rows =
      (map hd rows) :: (transp (map tl rows));

builtin function filter

fun filter pred []      = []
  | filter pred (x :: xs) =
       if pred x then (x :: filter pred xs)
                 else       filter pred xs;
filter (fn x => x mod 2 = 0) [1, 2, 3, 4, 5];

filter is bound as List.filter


using map and filter

val noisyAsciiList = [
    110, 101, 118, 101, 114, 32, 103, 111, 110, 110, 97, 32, 103,
    105, 118, 101, 32, 121, 111, 117, 32, 117, 112, 44, 32, 999, 
    110, 101, 118, 101, 114, 32, 103, 111, 110, 110, 97, 32, 108, 
    101, 116, 32, 121, 111, 117, 32, 100, 111, 119, 110, 999
];
    
String.implode (
	map (fn i => Char.chr i) (
    	filter (fn i => i >= 32 andalso i <= 126) 
        	noisyAsciiList));

using map and filter

a polynomial is represented as a list of $(coeff,degree)$ pairs

\[5x^3 + 2x + 7\]
type polynomial = (int * int) list;
val a = [(5,3), (2,1), (7,0)]: polynomial;

polynomial example

taking the derivative of a polynomial

fun derive (p: polynomial): polynomial =
    List.filter
        (fn (coeff, deg) => deg >= 0)
        (map
            (fn (coeff, deg) => (coeff * deg, deg - 1))
            p
        )
;

derive a;

find

fun find f [] = NONE
  | find f (x :: xs) = if f x then SOME x else find f xs;

Bound as List.find


foldl and foldr

builtin function foldl

fun foldl f init []      = init
  | foldl f init (x :: xs) = foldl f (f (x, init)) xs;

calculates $[x_1, x_2, … ,x_n] \rightarrow f(x_n, … ,f(x_2, f(x_1,init)))$

builtin function foldr

fun foldr f init []      = init
  | foldr f init (x::xs) = f (x, foldr f init xs);

calculates $[x_1, x_2, … ,x_n] \rightarrow f(x1, … ,f(xn-1, f(xn,init)))$

using foldl and foldr

Let’s redefine some functions…

fun sum l = foldl op+ 0 l;

fun reverse l = foldl op:: [] l;

fun xs @ ys = foldr op:: ys xs;

exists and all

builtin function exists

fun exists p []        = false
  | exists p (x :: xs) = (p x) orelse exists p xs;

Checks if the predicate p is satisfied by at least one element of the list

exists (fn x => x < 0) [1, 2, ~3, 4];

Bound as List.exists

builtin function all

fun all p []        = true
  | all p (x :: xs) = (p x) andalso all p xs;

Checks if the predicate p is satisfied by all elements of the list

all (fn x => x >= 0) [1, 2, ~3, 4];

Bound as List.all

all example

fun disjoint (xs, ys) =
  all (fn x => all (fn y => x <> y) ys) xs;

equality in polymorphic functions

Equality is polymorphic in a restricted sense

  • Defined for values constructed of integers, strings, booleans, chars, tuples, lists and datatypes
  • Not defined for values containing
    • Functions: equality is undecidable (halting problem)
    • Reals, because e.g. nan != nan
    • Elements of abstract types

equality types

ML has a polymorphic equality type ''a

op=;

Somewhat like an interface/trait in other languages


exam questions

exercise 1

Implement map using foldl

val foldl = fn : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b;
val map = fn : ('a -> 'b) -> 'a list -> 'b list;
fun map f inpList = foldl
    _
    _
    inpList
;
map (fn x => x * 2) [1, 2, 3, 4];

exercise 2

insSort (insertion sort) sorts a list according to a given less-then function.

val foldr = fn : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b;
val insSort : ('a * 'a -> bool) -> 'a list -> 'a list;
fun insSort lt inpList = foldr
    _
    _
    inpList
;
insSort (op<) [1, ~3, 5, 0];

exercise 3

fun upto m n = if (m > n)
    then []
    else m :: (upto (m + 1) n)
;

infix o;
fun f o g = fn x => f (g x);

exercise 3a

What will be printed?

val a = map (upto 2) (upto 2 5);

exercise 3b

What will be printed?

map
    (
        (fn f => null (f ()))
        o
        (fn t => fn () => tl t)
    )
    a
;

exercise 3c

What will be printed?

map
    (List.filter (fn t => t mod 2 = 0))
    a
;

exercise 4

Implement a tail recursive append

reminder:

infix @;
fun []      @ ys = ys
  | (x::xs) @ ys = x :: (xs @ ys);

exercise 4

fun append ...

NOTE:

fun aux ([], ys) = ys
  | aux (x :: xs, ys) = aux (xs, x :: ys);

fun append (xs, ys) = aux (aux (xs, []), ys);

exercise 5

Implement flatten using foldr

flatten : 'a list list -> 'a list;
fun flatten ...
[1, 2, 3, 4, 5, 6, 7, 8, 9] = flatten [[1, 2, 3], [4, 5, 6], [], [7, 8, 9]];

NOTE:

fun flatten xs = foldr (op@) [] xs;