Idiomdrottning’s homepage

Call-tables

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

Initial values

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

Popping that hood

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 collectors

The 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))

Custom accumulating

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)

Unary accumulating

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! support

You 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))

Starting with something

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)

call-key*

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)

Source code

call-table and call-table* are part of brev-separate.

git clone https://idiomdrottning.org/brev-separate