Generics in Scheme
In a previous note I demonstrated polymorphism in Scheme using Chez Scheme's compile time values and properties.
The code in that example is pretty raw; you might not want to code each generic in that manner. One approach is to decouple the signature/method table. We can do this by factoring out some code into a 'make-generic' procedure:
(define (make-generic table) (lambda (stx) (lambda (lookup) (syntax-case stx () ((_ param ...) (let ((signature (map (lambda (p) (lookup p (syntax type))) (syntax (param ...))))) (let ((proc-name (cdr (assoc signature table)))) (with-syntax ((proc-syntax (datum->syntax (syntax list) proc-name))) (syntax (proc-syntax param ...))))))))))
Now, to define the generic nth
from the example, we do:
(define nth-table '( ( (vector integer) . vector-ref ) ( (string integer) . string-ref ) )) (define-syntax nth (make-generic nth-table))
OK, let's switch gears for a second and talk about functors in Scheme. Here's a fold-left
functor for data structures which support operations nth
(random access) and size
.
(define (make-indexable-fold-left size nth) (define (fold-left seq val proc) (let ((n (size seq))) (let loop ((i 0) (val val)) (if (>= i n) val (loop (+ i 1) (proc val (nth seq i))))))) fold-left)
For example, to make a vector-fold-left
:
(define vector-fold-left (make-indexable-fold-left vector-length vector-ref))
So now, how do we define a generic fold-left
? From the functor definition of fold-left
, we can see that we'll need generic definitions of nth
and size
. We defined the generic nth
above. Here's size
:
(define size-table '( ( (vector) . vector-length ) ( (string) . string-length ) )) (define-syntax size (make-generic size-table))
Alright, now for our generic fold-left
:
(define-syntax fold-left (syntax-rules () ((fold-left seq ival proc) (let ((n (size seq))) (let loop ((i 0) (val ival)) (define-property i type 'integer) (if (>= i n) val (loop (+ i 1) (proc val (nth seq i)))))))))
Note that while it's based on the functor version, it's a macro. Why's it a macro? So the "types" of the paramaters can "propagate" down into the body of fold-left
. This propagation is required so that the generics size
and nth
can properly dispatch.
Here's an example using the generic fold-left
:
> (let ((v0 (vector 10 20 30)) (s0 "abc")) (define-property v0 type 'vector) (define-property s0 type 'string) (list (fold-left v0 0 +) (fold-left s0 #f cons))) (60 (((#f . #\a) . #\b) . #\c))
The examples here were run in Chez Scheme. Petite Chez Scheme can be downloaded for free.
That just doesn't feel right to me. Bolt something down in a macro if functions can't do the same thing, or for performance reasons.
fold-left-generic can be a function taking nth and size functions and returning fold-left-specialized as a closure. And then fold-left can have a case statement for different types of data types getting dispatched to different fold-left-specialized.
Thoughts?