f[d -> r](x) = { r if d = x, f(x) otherwiseLet's implement finite functions in Scheme:
(define make-empty (lambda () (lambda(x) #f))) (define extend (lambda (f d r) (lambda (x) (if (eq? x d) r (f x))))) (define lookup (lambda (f x) (f x)))We need
make-empty
because we're using an inductive
definition of finite functions and we need a base case.
The implementation of extend
simply follows the
mathematical definition above.
The reason that we use lookup
rather than
explicitly invoke f
is to provide abstraction
over the representation of finite functions.
By using lookup
, any client code that
uses this abstraction will know nothing about the
underlying implementation of finite functions as procedures.
Therefore we will be free to change or improve our implementation
of finite functions without making any changes to client code.
Now let's look at an alternative implementation of finite functions. But rather than pull a rabbit out of a hat, we're going to derive this new implementation from our existing one. This should give us reasonable confidence that the new implementation behaves the same way as the old one.
(define-record empty-ff ()) (define-record extend-ff (fun dom ran)) (define make-empty make-empty-ff) (define extend make-extend-ff) (define lookup (lambda (f x) (variant-case f (empty-ff () #f) (extend-ff (fun dom ran) (if (eq? x dom) ran (lookup fun x))))))To perform this transformation, we do the following:
make-empty
;
the other is built by extend
. Hence the two kinds of
records empty-ff
and extend-ff
.
Moving the actions of the two lambda expressions to where they are applied means moving the lambda bodies
#f and (if (eq? x d) r (f x))to the application
(f x)
in
lookup and to the application (f x)
in the procedure built by extend
.
Since there are two different kinds of records that represent
finite functions in the new implementation
(or two different lambda's that represent finite functions in the old),
we need to use
variant-case
at these two places.
The second application (f x)
within extend
might seem problematic because we need to move the procedure
built by extend
inside itself. To solve this
quandry, we use recursion!
Other procedural representations
We can use procedural representations for many other kinds of data.
For instance, we can represent pairs (i.e., cons, car, cdr
)
using procedures. A definition
of cons
follows.
(define cons
(lambda (x y)
(lambda (f)
(f x y))))
Exercise
- Provide implementations of
car
and cdr
such that:
(car (cons a b)) = a
(cdr (cons a b)) = b