A more convenient way to use hash tables.
A call-table is a lexically closed function wrapping a hash table.
Calling it with two arguments sets a key to that value.
(define flavor (call-table))
(flavor 'red 'strawberry)
(flavor 'yellow 'lemon)
(flavor 'green 'lemon-balm)
Calling it with one argument retrieves the value for that key.
(list
(flavor 'red)
(flavor 'yellow)
(flavor 'green))
⇒ (strawberry lemon lemon-balm)
You can change your mind and update existing keys:
(flavor 'green 'parsley)
(flavor 'green)
⇒ parsley
The initial value for unknown keys is #f.
(flavor 'chartreuse)
⇒ #f
You can set another initial value when creating the call-value.
(define ghosts-in-room (call-table initial: 0))
(ghosts-in-room 'attic 3)
(ghosts-in-room 'basement 2)
(apply +
(map ghosts-in-room
'(kitchen bedroom attic basement patio)))
⇒ 5
Abstraction is great when it helps us. We don’t want to put too many hoops in our own way.
You can access the underlying hash table by calling the call-table without any arguments.
(hash-table? (flavor))
⇒ #t
You can swap out the underlying hash table by using the keyword
update:
and a hash table. This is currently the only key with a
special meaning.
(flavor update:
((over (cons (cdr x) (car x)))
(flavor)))
(list
(flavor 'strawberry)
(flavor 'lemon)
(flavor 'parsley))
⇒ (red yellow green)
It specifically only listens for the combination of the keyword
update:
and the value type hash-table
. Other key/value pairs
aren’t treated specially.
(flavor update: "a string")
(flavor update:)
⇒ “a string”
call-table*
— value collectorsThe vanilla call-table
replaces its values. Sometimes, you instead
want to keep accumulating new values.
(define flavor (call-table*))
(flavor 'red 'strawberry)
(flavor 'blue 'blueberry)
(flavor 'red 'raspberry)
(list (flavor 'blue)
(flavor 'red))
⇒ ((blueberry) (raspberry strawberry))
The default is to cons the new values onto a list of the old ones.
You can change that:
(define ghosts-in-room (call-table* proc: + initial: 0))
(ghosts-in-room 'attic 3)
(ghosts-in-room 'basement 2)
(ghosts-in-room 'attic 1)
(map ghosts-in-room '(kitchen bedroom attic basement patio))
⇒ (0 0 4 2 0)
Sometimes, it’s even useful to just have a unary procedure.
(define ghost-counter (call-table* proc: add1 initial: 0 unary: #t))
(ghost-counter 'garden)
(ghost-counter 'gazebo)
(ghost-counter 'garden)
With unary procedures, while each call returns the new value (which can often be useful enough), there’s no read-only idempotent way to just retrieve values unless you access the entire hash table.
(hash-table->alist (ghost-counter))
⇒ ((garden . 2) (gazebo . 1))
But that’s where our under-the-hood access can come in handy:
(ghosts-in-room update:
((as-list append) (ghosts-in-room) (ghost-counter)))
(hash-table-for-each
(ghosts-in-room)
(lambda (room amount)
(print "There were " amount " ghosts in the " room ".")))
There were 4 ghosts in the attic.
There were 1 ghosts in the gazebo.
There were 2 ghosts in the garden.
There were 2 ghosts in the basement.
set!
supportYou can also use set!
to set slots.
On a call-table*
, the new values are added to the accumulators,
rather than replacing them.
(set! (flavor 'red) 'watermelon)
⇒ (watermelon raspberry strawberry)
You can also swap out the underlying hash-table.
(set! (flavor) (make-hash-table))
You can include an alist or a hash-table while first creating your call-table.
(define horse
(call-table
seed:
'((epona . zelda)
(sundance . g1)
(stridor . motu))))
(horse 'epona)
(horse 'hero 'phantom)
(horse 'hero)
Sometimes you think call-table
is convenient but you only need one key.
For call-key
, just use make-parameter
.
But call-key*
is awesome since it accumulates its values.
It has the same proc
, unary
, and initial
keyword arguments as
call-table*
. It doesn’t have seed
because the idea is that you
just use initial
. The generated procedure has update
(which
takes a new list as argument) and get
(which you only need for
unary
call-keys).
(define horses (call-key*))
(horses 'ruby)
(horses 'kind-girl)
(horses 'tornado)
(horses)
⇒ (tornado kind-girl ruby)
call-table
and call-table*
are part of brev-separate.
git clone https://idiomdrottning.org/brev-separate