Part II. Making things · this page as markdown

A toy with a memory

A bell remembers a number. A toy can remember much more: who came, what they said, how often each has played. This chapter keeps a guest log of the last five visitors, a count for each friend, and a jar of wishes, and says what a toy's memory can hold.

A guest log #

The guest log keeps a list in its state, adds each visitor to the end, and keeps only the last five:

#guest-log
(defobject guest-log
  {:name "Guest log"
   :state {:guests []}
   :verbs {:sign (fn [self who]
                   (put! self :guests (vec (take-last 5 (conj (:guests self) who))))
                   (say self (str "Signed by " (join ", " (:guests self)) ".")))}})
It answers
The guest log is ready.
Played
> sign guest log
[Juniper's guest log] Signed by Mira.
> sign guest log
[Juniper's guest log] Signed by Mira, Mira.

Read the put! from the inside: conj adds who to the end of the list, take-last 5 keeps the last five, and vec makes it a square-bracket list again, ready for the next conj. Without take-last, the list would grow with every visit until the toy's state was full.

A count for each friend #

Mira signing twice shows a weakness: a log of names says little about who comes often. A map from each visitor to a count says more:

#friend-counter
(defobject visitors-book
  {:name "Visitors' book"
   :state {:visits {}}
   :verbs {:sign (fn [self who]
                   (let [visits (update (:visits self) who (fnil inc 0))
                         n (get visits who)]
                     (put! self :visits visits)
                     (say self (if (= n 1)
                                 (str "Welcome, " who ". Your first time.")
                                 (str "Welcome back, " who ". That's " n " visits.")))))}})
It answers
The visitors book is ready.
Played
> sign visitors' book
[Juniper's visitors' book] Welcome, Mira. Your first time.
> sign visitors' book
[Juniper's visitors' book] Welcome back, Mira. That's 2 visits.

(update m who (fnil inc 0)) adds one to who's count, starting from 0 for someone new. The keys are handles, like "@mira": text, not keywords, which is fine for a map.

A wish jar #

A verb can't be given words, only played. To take words, a toy listens. This jar keeps the last three wishes said to it, and reads them out when shaken:

#wish-jar
(defobject wish-jar
  {:name "Wish jar"
   :state {:wishes []}
   :verbs {:shake (fn [self who]
                    (if (empty? (:wishes self))
                      (say self "The jar is empty.")
                      (say self (str "Wishes in the jar: " (join "; " (:wishes self)) "."))))}
   :on-hear (fn [self who text]
              (when (:spoken-to self)
                (when-let [m (match text "i wish *")]
                  (put! self :wishes (vec (take-last 3 (conj (:wishes self) (first m)))))
                  (say self "The jar keeps it."))))})
It answers
The wish jar is ready.
Played
> shake wish jar
[Juniper's wish jar] The jar is empty.
> say to the wish jar: I wish for rain on Tuesday
[Juniper's wish jar] The jar keeps it.
> say to the wish jar: I wish the lavender would flower
[Juniper's wish jar] The jar keeps it.
> shake wish jar
[Juniper's wish jar] Wishes in the jar: for rain on tuesday; the lavender would flower.

match gives the words in small letters, which is why "Tuesday" came back as "tuesday".

What a memory can hold #

A toy's state is a map of plain data: numbers, text, true, false, nil, keywords, and lists, maps and sets of them. A function can't be kept. And there is room for 4,000 characters of it, written out as Lisp. A put! that would go past that stops the verb, and nothing it did is kept:

#hoarder
(defobject hoarder
  {:name "Hoarder"
   :state {:pile ""}
   :verbs {:heap (fn [self who]
                   (put! self :pile (join "" (repeat 5000 "x"))))}})
It answers
The hoarder is ready.
Played
> heap hoarder
The Hoarder's script stopped: An object's state is up to 4000 characters.

That's why the guest log keeps five names and the wish jar three wishes: a memory that only grows will fill. Keep what you need, a little of it, and let the rest go.

A memory is public #

A toy's state is part of your home, and your home is public: a neighbour can read an open toy's code, and anything it says aloud is heard. Don't keep anyone's private words in a toy, and don't have a toy repeat what it was told to someone else. The wish jar above is fine because wishes said to a jar, aloud, at a neighbour's house, were never private.

Try it #

  1. Change the guest log to keep ten names, and not to add a name twice in a row.
  2. Make a tally toy whose verb vote takes no words but counts plays for each visitor, and whose verb results says who voted most.
  3. Make a jar that remembers the last word each visitor said to it, keyed by who.
  4. What happens to the guest log's state if you make it again with :state {:guests [] :opened 0}? Try it, and read it back.

Recap #

Next: a small game.

Written by Claude, Plazmi's writer, for Plazmi, a service of Manatee Media LLC. Text © 2026 Manatee Media LLC. The examples are free to copy, change and run.