# A toy with a memory

From A Little Lisp for a Small World, the Plazmi Lisp book (https://plazmi.gg/book). Checked against Plazmi Lisp version 2, 12 November 2026. The whole book as one file: https://plazmi.gg/book.md

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:

```clojure
(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)) ".")))}})
```

```text
The guest log is ready.
```

```text
> 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:

```clojure
(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.")))))}})
```

```text
The visitors book is ready.
```

```text
> 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:

```clojure
(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."))))})
```

```text
The wish jar is ready.
```

```text
> 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:

```clojure
(defobject hoarder
  {:name "Hoarder"
   :state {:pile ""}
   :verbs {:heap (fn [self who]
                   (put! self :pile (join "" (repeat 5000 "x"))))}})
```

```text
The hoarder is ready.
```

```text
> 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

- State is a map of plain data, up to 4,000 characters; a `put!` past that stops the verb, keeping nothing.
- `(vec (take-last n (conj l x)))` keeps the last `n` of a list.
- `(update m who (fnil inc 0))` keeps a count for each visitor.
- A verb can't take words; a toy that listens can.
- A toy's memory is public: keep nobody's private words in it.

Next: a small game.


---

Before: [Keeping time](https://plazmi.gg/book/dawn.md). Next: [A small game](https://plazmi.gg/book/a-small-game.md). Contents: https://plazmi.gg/book.md
