I use call-tables all the time in my code. Sometimes a good solution is to have two call-tables with the same data, one going from A to B and the other going from B to A. That way I can find anything from anything.
Here’s where they came from!
I was a li’l disappointed in Software Design for Flexibility since I think SICP is the most important philosophical text of the 20th century (sorry Wittgenstein and Sartre, but Eva Lu Ator is where it’s at).
SDfF is actually a good book and I can recommend it, just, it’s a normal good book whereas SICP is like the heavens parted. I guess part of the problem is me.
Georg Lichtenberg once said “A book is a mirror. When a monkey looks in, no apostle looks out”. And when I read SICP I had a lot to learn but when I read SDfF twenty years later I had stuff pretty much figured out and what was in it didn’t felt like super new ideas to me and some of them were stuff that felt completely unworkable since they’d require going over all imported functions and recording their arity and things like that, only to get slightly shorter, slightly less readable code.
But as I implemented that arity
recorder, I realized that I could
extract the body out to a more general create-arity
such that:
(define arity (create-arity))
(arity cons 2); => 2
(arity cons); => 2
I thought arity
might not be the only thing I’d wanna create with
that thing. Me and jcowan had made great hay out of using hash-tables
to record nodes in tree.scm. Having a way to bind anything to
anything with any equivalence function is awesome!
So I renamed it call-table
and shipped it!
Any time you have nodes or vertices of any kind, spray on some call-tables and boom, now you have edges. It’s a more flexible design than what was in Software Design for Flexibility.♥︎
The ability to open the hood and access the underlying hash-table was there from day one. Astractions with escape hatches for the win. Just like how Lisp has lists that you can do list processing on but you can easily access the underlying cons cells.
Chicken already had callable-data-structures which I vaguely
remembered the day after making call-tables. I feel doubly
guilty to Mario for not only had I ended up biting his schtick with
these call-tables but also later on ended up calling a module mdg
(for “match-define generics”), which was pure coincidence. I’m sorry,
Mario!
When I looked at that again, I saw that they exactly the same api for
accessing the data in the hash-table and for accessing the underlying
hash-table but what’s so clutch with the way SDfF’s arity
and other
call-tables work is that you can also easily and side-effectively
change what’s in the table. It’s not just callable on the reading end,
it’s also callable for setting.
Callable-data-structures did have one awesome feature that I hadn’t
implemented then: support for using generalized set!
. I added that
after but I haven’t actually used it myself yet even though I use
call-tables all the time.
For a while I was doing a lot of Clojure work and unlike some other
lisp, they use tables all the time, although the paradigm there
is usually not to use them as an arity
-like store but instead use
them in a purely functional way, shadowing them rather than changing
them, which is cheap on Clojure because of how they’re implemented
(so, on Clojure, the shadow can safely share parts of the original
table).
That’s not something I have implemented for mine yet. It’d require swapping away the underlying datastructure from SRFI-69 hash-tables to something more custom, that shares data until nodes are changed in which case it splits those nodes specifically and shares the rest.
One thing I did find useful in Clojure though was being able to write out a table and its contents right in the code, so I implemented the call-table literals.
Like I could (define arity (ct cons 2 not 1 reduce 3))
and it’d then
be able to know that (arity cons)
is 2 and I can still add new stuff
to arity
like any other call-table.