When using the define
from match-generics you need to take some
care about what order you define the calls.
If the things are clearly different, like different numbers of arguments or conflicting predicates or different static data, like
(define (foo bar 4) (+ bar 4))
(define (foo bar 5) (* bar 5))
then that’s fine, those can go in any order, but if you have something that’s a superset of something else, that’s when you need to put the supersets first and the special cases last. Like,
(define (foo bar anything) (- bar anything))
(define (foo bar 4) (+ bar 4))
(define (foo bar 5) (* bar 5))
is right. If you would’ve put the “anything” one last, it’d shadow the special 4 and 5 ones.
This is a huge pain and it even ships with a way to reset things at the REPL if you’ve borked things up:
(define: reset foo)
I even have an Emacs shortcut to define the definition at point, that’s how annoying this is for when you’ve made mistakes.
It’s a lot better in version 2 of match-generics since it is a lot smarter at recognizing you when you’re overwriting something that it already knew, and that can fix some typos, but things like misspelled predicates is still grounds for a reset.
Some other generics system use type classes to automatically be able to order things based on supersets first, it basically has a database of every possible type of arg and whether or not that arg can be a subperset of something else more specific.
With brev, you can do one thing to numbers and another to integers by putting the special case last:
(define (foo (? number? bar)) ...)
(define (foo (? integer? bar)) ...)
In those other typeclass-based systems, those could’ve gone in any order and it’d just do the right thing, but with match-generics, you need to mind the order.
What you gain from this is a lot more flexility. You can do any predicate, even custom ones, and custom destructuring too. Since you supply the order, it doesn’t have to be able to figure out the order.
It happens plenty often as I write documentation and I write about some special hoop or requirement the programmer needs to go through, or some case the library doesn’t handle automatically, and I’m like so ashamed and I think “wow, I can’t ship this” and I go back to the drawing board and implement the missing thing. But this time, the trade-off (given Scheme’s limitations) is just super worth it.
That started out as just how I implemented my first prototype using consing, but I immediately liked it because it lets you add special cases later on that you maybe didn’t think of at first.
Like “Oh, I need to be able to handle numbers, too”, OK, that’s no problem.