[ ugh... need to decide what's technically currying and what isn't... and provide a precise definition.]
Above I showed that we can "specialize" a procedure by having it take an argument that specifies an action to take. It is often useful to have a procedure that can create procedures of some general type, producing a specialized procedure each time it's called.
For example, rather than having to specialize mem by hand,
we can provide a procedure that automates the process. This
procedure make-mem-proc will take a comparison routine
as an argument, and return a specialized version of mem that
uses that procedure.
(define (make-mem-proc pred?)
(lambda (target lis)
(mem pred? target lis)))
Each time this procedure is called, it will bind its argument variable
pred?, and create a new procedure that will call mem.
Each new procedure will "remember" the binding of pred? that was
created for it, so each one can do something different.
Now we can define member, memv, and memq, by
using this procedure to create three new procedures, each with
its own captured binding of pred?.
(define member (make-mem-proc equal?)) (define memq (make-mem-proc eq?)) (define memv (make-mem-proc eqv?))
(Notice that we're using plain variable definition syntax here. We're
just defining variables member, memq, and memv,
and initializing them with procedures (closures) returned by
make-mem-proc.)
Of course, if we only use mem in this way, then we don't actually need
separate mem and and make-mem-proc procedures.
We can just write make-mem-proc using a lambda expression
that's equivalent to mem:
(define (make-mem-proc pred?)
(letrec ((mem-proc (lambda (thing lis)
(if (null? lis)
#f
(if (pred? (car lis) thing)
lis
(mem-proc pred? thing (cdr lis)))))))
mem-proc))
Here I've used a letrec so that the procedure will be able
to call itself recursively. Each time we call make-mem-proc,
it will bind its argument pred?, initializing it with the
procedure argument we pass. Then it will bind mem-proc
and create the specialized procedure using lambda. Note
that the bindings of both pred? and mem-proc will
be remembered by the closure created by lambda. This
allows the new closure to see both the predicate it should use,
and itself, so that it can call itself recursively.
[ A picture would be nice here, showing what we get
when we define mem, memq, and memv
using make-mem-proc... three variable bindings,
holding three closures, each of which is closed in
an environment with its own binding of mem-proc
scoped inside its own binding of pred?. ]
There are two advantages to coding make-mem-proc this way.
One is that it avoids cluttering up our code with a definition
of mem that's external to make-mem-proc.
[ another advantage is that a good compiler will be
able to optimize the code better, because it can tell that the
value of a bindings of pred? or mem-proc will
never change once the binding is created. It may use that
information to generate better code... ]