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)
You can also hack this to get a call-table that loves to add new stuff but refuses to overwrite what’s already there:
(define obstinate (call-table* proc: (fn (eif y y x))))
(obstinate 'a 13)
⇒ 13
(obstinate 'b 14)
⇒ 14
(obstinate 'a 15)
⇒ 13
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