Mathematical Functions

A Brief Refresher on Functions

In computer programming, we speak often of functions. Have you ever wondered about the relationship between functions in computer programming and the functions you learned about in math class back in grade school?

You all know what a function in computer programming is: a piece of code that, for a given input value, returns an output value. All of the Racket functions we've written this semester work this way, for example:

(define square
  (lambda (n)
    (* n n)))

We can think of a program's function as a machine that converts input values into output values:

a box labeled square, with an arrow entering on the left labeled 'n' and arrow exiting on the left labeled 'n**2'
a black-box depiction of the function square(n) = n2

In our math classes, we would have written this as:

f(n) = n2

In math, a function is a mapping from values in one set, the domain, to values in another set, the range. Each value in the domain may be associated with at most one value in the range. That is, for each value in the domain, there is a unique value in the range.

This has been true of all the functions in our programs this semester, too. One primitive "function" thus far has behaved differently, Racket's random. It returns a different value each time it is called. This is one of the reasons sub-programs in Racket are more precisely called procedures rather than functions: they do not have to return a unique value for a given input.

In mathematics, a functions is often represented as a set of ordered pairs, for example:

f = {(1, 1) (2, 4) (3, 9)}

f = {(foo, 2) (bar, 5) (baz, 15)}

If two pairs in the set have the same first value, then the set is not a function; it is a relation. A function is a relation, but not vice versa.

Some functions are infinite sets. Their domains are infinite sets. For such functions, we often define a rule that describes the mapping from domain to range. For example, the set:

f = { (x, y) | x is a real number and y is x² }

is an infinite function. Indeed, it is the mathematical function that corresponds to our square procedure above!

So: a function in math and a function in a program can and often do describe the same set of mappings. One is a data value (a set), and the other is a procedure. What does this tell you?

Finite Functions

A finite function is a function that has a finite domain. (By inference, it also has a finite range.) A finite function can always be written as a finite set of ordered pairs, though in practice this is often tedious or impractical. For example,

f = {(foo, 2) (bar, 5) (baz, 15)}

is a finite function. If f contained 500 pairs, then we might not want to write it down by hand!

Finite functions play an important role in computer science because we often want to create mappings between two finite sets of objects.

More generally, many modern languages provide finite functions as a primitive data type. For example, Python defines a dictionary datatype, and Java defines a Map interface for its many classes that map keys to values. You might think of such data structures as generalized arrays, where the index is not limited to being a non-negative integer. The index can be an arbitrary object. The rise of scripting languages such as Python, Perl, and Ruby have made finite functions a workhorse of modern programming, in the form of hashes and dictionaries.

With finite functions being so useful to us in computer science, it is not surprising that we will want to use them in our language processors. Scheme is a minimal language and does not provide this data type as a primitive. Racket offers a hash that can be used as a finite function. Let's implement the data type for ourselves, so that we can see how it works. Our implementations will be lightweight and perfect for use in our intepreters.

But how to implement it? Our discussion above tells us that there are at least two different ways: functions and data.