Skip to content

Instantly share code, notes, and snippets.

@dharmatech
Created October 17, 2010 23:37
Show Gist options
  • Save dharmatech/631450 to your computer and use it in GitHub Desktop.
Save dharmatech/631450 to your computer and use it in GitHub Desktop.

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.

@whtajoke990
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment