chickadee » match-generics

match-generics

Here are some flexible and fast generics.

They don't need to have the same number of arguments or use the same names:

(define-generic (frotz a b c) (list b a c))
(define-generic (frotz x y) (+ x y))
(list (frotz 1 2 3) (frotz 4 5))

⇒ ((2 1 3) 9)

They can even destructure their arguments:

(define-generic (my-map proc (x . xs))
  (cons (proc x) (my-map proc xs)))

(define-generic (my-map proc ()) '())

(my-map add1 '(1 2 3))

⇒ (2 3 4)

To use predicates, use the form (? pred var), like this:

(define-generic (plus (? list? a) (? list? b)) (append a b))

(define-generic (plus (? string? a) (? string? b)) (string-append a b))

(define-generic (plus (? number? a) (? number? b)) (+ a b))

(list
 (plus "13" "14")
 (plus 7 22)
 (plus 13 19)
 (plus '(a b c) '(1 2 3)))

⇒ ("1314" 29 32 (a b c 1 2 3))

You need to define them in order from least specific to most specific, i.e. define the most specifics last and the fallbacks first.

You can even use nested defines! But if you do, each need to use the same car positions. Only the cdr position (a.k.a the arguments) is generic.

(define-generic ((frobnicate a) b z) (list a b z))
(define-generic ((frobnicate a) b) (string-append a b))

(let ((hoho (frobnicate "hoho")))
  (list (hoho 1 2)
        (hoho " and such")))

⇒ (("hoho" 1 2) "hoho and such")

Implementation

By now, maybe some of the long-time readers in the audience has guessed what is going on.

Each generic is defined using a clause from matchable!

The implementation using some miscellaneous brev-separate stuff is just… five lines!

(define-for-syntax gentable (call-table*))
(define-ir-syntax*
  (define-generic (name . pattern) . body)
  (cons 'match-define
        (gentable (strip-syntax name) (cons (cons name pattern) body)))))

For source code,

git clone https://idiomdrottning.org/match-generics