content="Data-structures and algorithms in Standard ML. Notes from a lecture course given by Michael Fourman at the University of West Australia, Spring 1994." /> rel="start" type="text/html" href="/mfourman/index.html" title="Michael Fourman" /> Functions as Data

## Functions as Data

October 31, 2006

In ML functions are first-class objects: they can be passed as arguments, returned as values, and incorporated as components of compound values. Sometimes this can be an effective way of representing data; in particular, it allows a common interface to be provided for similar functions on differently represented datatypes. By including these functions as part of the representation of an object, we can implement some features of an “object-oriented” programming style in ML. We will return to this use of functions as data later in the course. We can also use functions to represent infinite datastructures, such as the sequence of all prime numbers. Again, we will return to this topic later in the course.

In this note, we are more interested in the use of functional representations to provide “executable specifications” of some datatypes that we will later implement in a more traditional fashion. We give some examples that make use of functions as values.

### Dictionaries

Our first example is an implementation of the Dictionary signature.

signature DictSig =
sig
type Key
type Item
type  Dict
exception Lookup
val empty  :  Dict
val lookup :  Dict -> Key -> Item
val remove : Key *  Dict ->  Dict
val enter  : (Key * Item) *  Dict -> Dict
end

 Figure 1: A signature for Dictionaries

Consider a dictionary, like the telephone dictionary, with strings as keys, and numbers as entries. Given a string, we can use the dictionary to lookup the corresponding number. This gives us a (partial) function from strings to numbers. We can use functions to implement dictionaries directly.

structure Dict : DictSig = struct
exception Lookup
type Key  = string
type Item = int
type Dict = string -> item
val empty = fn _ => raise Lookup

fun lookup d k = d k

fun remove (k, d) =
fn k’ => if k’ = k then raise Lookup
else d k’

fun enter ((k, e), d) =
fn k’ => if k’ = k then e
else d k’
end;

 Figure 2: A Dictionary implemented as a function

.

A more common implementation of a dictionary is as an association list, a list of associated pairs. Mathematicians call the set of such pairs the graph of the function; this may be confusing, as in computer science we use the term graph for a related, but more general, notion.

structure Dict : DictSig =
struct
exception Lookup

type Key  = string
type Item = int
type Dict = (string * int) list

val empty = []

fun lookup [] k       = raise Lookup
| lookup ((k’, e) :: t) k = if k’ = k then e else lookup t k

fun remove (k, []) = []
| remove (k, (k’,e) :: t) = if k = k’ then t
else (k’, e) :: remove k t

fun enter ((k, e), d) = (k,e) :: remove k d
end;

 Figure 3: A Dictionary implemented as an association list

### Sets

A set may be represented by a boolean-valued function, or predicate, of type Item -> bool, that tells us whether a given item is a member of the set.

infix 4 ==
functor FNSET( type Item val == : Item * Item -> bool ) =
struct
type Item = Item
type Set  = Item -> bool

val empty  = fn _ => false
fun member  s e = s e

fun insert(e, s) =
fn e’ => if e == e’ then true else s e’

fun delete(e, []) =
fn e’ => if e == e’ then false else s e’

fun union (s, t)     = fn e => s e orelse t e

fun intersect( s, t) = fn e => s e andalso t e
end;

 Figure 4: Sets implemented as predicates

This gives an implementation of most of the set operations. However, we cannot implement IsEmpty, since there is no way to test whether a given function will always return the answer false. One benefit of this representation is that it allows us to represent infinite sets, such as the set of even numbers.