GitHub Repo

A quick test of a simple idea seems good. I am pleased.

I was sitting at the computer in a chat room, and a bit bored, so I opened Paper™ and drew a little picture. Nothing fancy, but it led to an idea.

sketch showing imperative and some scribbles

The idea is simple, and it works, but it does raise a small design concern.

The basic insight is that our lambdas have access to the Imperative: it’s their parameter and it contains the world and room, which are needed to get things done. Therefore, our lambdas can send messages to the Imperative.

Now the Imperative could know how to get things from the room and world, but that seems invasive. Instead, I decided that the command would call a new act method, this one receiving both the world Actions and the room Actions. It would set a flag, call the room lambda, and only call the world lambda if the flag gets reset. We’d put the reset in the default lambda, saying that the room didn’t do the job.

Right now, it looks like this:

class Room ...
    private val actionMap = mutableMapOf<Phrase,Action>(
        Phrase() to {imp -> imp.roomDone = false}
    )

    fun command(command: Command, world: World) {
        world.response.nextRoomName = roomName
        val phrase: Phrase = makePhrase(command, world.lexicon)
        val imp = Imperative(phrase,world, this)
        imp.act(actions, world.lexicon.actions)
    }

data class Imperative(
    val phrase: Phrase,
    val world: World = world(){},
    val room: Room = Room("fakeroom")
)  {
    var roomDone: Boolean = false

    fun act(roomActions: Actions, worldActions: Actions) {
        roomDone = true
        act(roomActions)
        if (!roomDone) act(worldActions)
    }

Now looking at that, we can see right away that we should invert the sense (and the name) of the Boolean. But it works just fine and now the test that was saying “unknown command” doesn’t say that, because the “look around” command doesn’t reset the flag. The default for an actual Room Action should be to leave the flag alone. You’d only reset it in the case of the room not handling the command (the usual case) or if you handled the command but wanted to pass the command on to the world as well. Perhaps a snide comment before allowing the command to go through.

But I do have a small concern. The Imperative is now mutable. It changes its internal state. Of course, it quickly gets thrown away and we get a new Imperative next time, but in general immutable objects are “better”, in that they are easier to understand. This one is a perfect example: its behavior now varies based on its internal state. It requires just a bit more thought when we work with it.

That said, the harm seems small and the benefit seems large. Furthermore, we can clearly extend how this behaves, all internally to the relationship between the lambdas and the Imperative, should we need to.

So I like it. I don’t like that boolean: it’s backward. And probably we should cover it with a message to the imperative. Let’s call it notHandled. We’ll set our default in the Room to call that:

    private val actionMap = mutableMapOf<Phrase,Action>(
        Phrase() to {imp -> imp.notHandled()}
    )

In Imperative, we just deal with receiving that. Let’s reverse the boolean first.

IDEA allegedly has an Invert Boolean refactoring, but I can’t find a place to click or select where it is enabled. Of course I can just do it, but I’d like to learn the tool if I can. No luck. Do by hand:

data class Imperative ...
    var worldNeeded: Boolean = true

    fun act(roomActions: Actions, worldActions: Actions) {
        worldNeeded = false
        act(roomActions)
        if (worldNeeded) act(worldActions)
    }

    fun notHandled() {
        worldNeeded = true
    }

Tests are green. Commit: Room and World actions properly handled by new method Imperative(roomActions, worldActions).

Sweet. Very easy. I’m surprised that I didn’t see it sooner. However, things like this are easier to think of when we’ve cleared the brush away and made things move visible. It does help if we’re heading in the right direction and clearing the brush where it will help, of course. That’s why thinking is so useful. It’s also why reading the code is so useful.

Anyway, nice little afternoon pickup. See you next time!