I have an idea for something that might be better. But that’s not my reason for being here. (And should all ojbects be written for their own convenience? Yes, and …)

It’s only about 0700 and I just had to get up. I was thinking about the world and where it’s going. I was trying to imagine writing some fiction, which I’ve always sort of wanted to do—shut up, agile haters—and every idea I had turned dystopian within minutes. So I’m going to code, hoping that my program doesn’t turn dystopian.

Although—the current spec for the robots is a bit dystopian all on its own. We need a better robot game, one that is cooperative and building something rather than where our mission is apparently to be the last robot standing.

We’re working on that, slowly. Today, I just want to work some code. If I were a potter, I’d just want to get my hands in the clay this morning. My clay is code. Which is nice: my hands stay relatively clean.

Requests, not Method Calls

When I started building this program, I thought that the model of Robot calling methods on the World was a sensible design. In fact, I still think it’s a sensible design. As a rule, I find that objects work best when they are fashioned around collaboration, with methods and returns that make sense to the objects we have.

The spec for this program, and for many such programs, is a client-server kind of thing, where the client sends messages to the server, and the server sends messages back, and the messages themselves are defined in some form that no one really likes. In our case they are structured objects, dictionaries, with named elements containing values, or other dictionaries, all coming down in the end to the parameters we’d like to just be passing in, and the objects we’d like to get back.

When those two ideas, methods and returns, and formatted messages, come together (which happens to be playing right now on the Beatles channel), the best thing I know is to build an adapter object that translates what I’d like to say into what I have to say, and translates what comes back into what I wish would come back.

In the current program, most of that is going on in the WorldProxy. We could call it WorldAdapter if we wanted to name it after the pattern.

Current Code

When I started out this morning, I was thinking of the code we have that looks like this in the Robot cllient:

function Robot:keyboard(key)
    if key == "l" then
        self:look() -- look
    elseif key == "w" then
        self:forward(1)
    elseif key == "s" then
        self:back(1)
    elseif key == "d" then
        self:turn("right")
    elseif key == "a" then
        self:turn("left")
    end
end

function Robot:look()
    local callback = function(response) self:lookCallback(response) end
    self._world:look(self._name,callback)
end

function Robot:lookCallback(response)
    local packets = LookPacket:fromResponse(response)
    for i,packet in ipairs(packets) do
        self:addLook(packet)
    end
    self:standardResponse(response)
end

And like this in the WorldProxy:

function WorldProxy:look(name, callback)
    local request = {
        robot=name,
        command="look",
        arguments={}
    }
    local responseDict = self.world:request(request)
    callback(Response(responseDict))
end

I was thinking what I now believe (would you believe the channel just played “I believe in yesterday” from “Yesterday”?) … I was thinking what I now believe was a mistake, along the lines of

Well, we’re always building these requests, maybe we should create them in Robot.

No, I think that’s mistaken. The Robot should be written for its own convenience, as should every object. We can find some improvements to what we have, but pushing the request over into Robot would not be an improvement by my standards.

Do I really mean that? Shouldn’t an object be written for the convenience of its users? Yes, that’s true as well, but internally, the object should be clean and simple, and it should be doing easy things. If it’s complex, there’s a good chance that it needs help from other objects. When we work this all the way down, we tend to get lots of small objects, and almost all of them are simple, clear, and do things that are quite straightforward.

Now yesterday I actually moved the LookPacket code from WorldProxy back over to Robot. I think that’s a mistake. Look at that callback again:

function Robot:lookCallback(response)
    local packets = LookPacket:fromResponse(response)
    for i,packet in ipairs(packets) do
        self:addLook(packet)
    end
    self:standardResponse(response)
end

There’s something that I don’t like about that … the code wants a collection of LookPackets, because that’s what it thinks would be nice to have. So that’s fine. But the code also expresses that the response doesn’t provide the LookPackets … instead LookPackets have to create themselves by trolling through the Response somehow.

Now that is in fact the case: we do need to troll through the response a bit. But we don’t have to stare right at that ugly bit of reality. What I’d like is for that method to look more like this (and I’m going to make that happen right now):

function Robot:lookCallback(response)
    self:updateMap(response)
    self:standardResponse(response)
end

function Robot:updateMap(response)
    local packets = LookPacket:fromResponse(response)
    for i,packet in ipairs(packets) do
        self:addLook(packet)
    end
end

OK, that’s better. Composed Method pattern on the lookCallback. Test. Green. Commit: Apply Composed Method to Robot:lookCallback. Pull out updateMap.

But I still don’t think that we should have to make those packets. We wish that the response just had them. We want to say this:

function Robot:updateMap(response)
    for i,packet in response:lookPackets() do
        self:addLook(packet)
    end
end

Well, I just typed that in. It won’t work, but if Response knew lookPackets …

function Response:lookPackets()
    return ipairs(LookPacket:fromResponse(self))
end

I rather think that’s going to work. And it does. Green. Commit: Robot updateMap uses Response:lookPackets method.

OK, let’s look at that as a whole:

function Robot:look()
    local callback = function(response) self:lookCallback(response) end
    self._world:look(self._name,callback)
end

function Robot:lookCallback(response)
    self:updateMap(response)
    self:standardResponse(response)
end

function Robot:updateMap(response)
    for i,packet in response:lookPackets() do
        self:addLook(packet)
    end
end

function Response:lookPackets()
    return ipairs(LookPacket:fromResponse(self))
end

What’s not to like? Well, I don’t like the i being there. We could give it an _ name to signify that it doesn’t matter. We could build a special iterator that just returns the packets. We could pass a function to Response and let it do the work. That would look something like this:

function Robot:updateMap(response)
    local add = function(packet) self:addLook(packet) end
    response:looksDo(add)
end

If the function notation in Lua was more compact, I might prefer that. As it is, it’s not more clear. What about this format:

function Robot:updateMap(response)
    response:looksDo(function(packet) self:addLook(packet) end)
end

Well, maybe. I’ll allow it. Let’s do the looksDo method.

function Response:looksDo(f)
    for i,p in self:lookPackets() do
        f(p)
    end
end

Test. Green. Commit: Refactor updateMap to use WP:looksDo.

Let’s reflect.

Reflection

We now have this in Robot:

function Robot:lookCallback(response)
    self:updateMap(response)
    self:standardResponse(response)
end

function Robot:updateMap(response)
    response:looksDo(function(packet) self:addLook(packet) end)
end

We used to have this:

function Robot:lookCallback(response)
    local packets = LookPacket:fromResponse(response)
    for i,packet in ipairs(packets) do
        self:addLook(packet)
    end
    self:standardResponse(response)
end

Better? I think so. The Robot still gets to say what it wants to do about looks, but it doesn’t have to know where they come from or how they’re created. That seems to me to be a better arrangement. I’d like it even better if passing in a function parameter was a bit less clunky, but that’s not bad at all. If we did more of it, we’d become more and more used to it and would probably like it even better.

I’m less than an hour in. Let’s sum up, publish this, and then maybe do more.

Mini-Summary

Focusing for just a bit on a small bump in the code has resulted in simplified Robot code, and the Response code isn’t bad either:

function Response:looksDo(f)
    for i,p in self:lookPackets() do
        f(p)
    end
end

function Response:lookPackets()
    return ipairs(LookPacket:fromResponse(self))
end

A nice little improvement, and something to think about for other loops in the future.

The larger lesson has to do with “feature envy”, the tendency to write code that pulls bits out of other objects and processes them. It’s usually better to ask those other objects to do more of the work, as we did here. When I’m writing in Codea Lua, I seem often to do that sort of thing. I blame my long experience in procedural languages.

Except that I don’t blame anything or anyone. I just try to see how I can have a bit more fun by doing things a bit better. To me, that’s where the joy of programming can be found

And it appears I’ll never run out of ways to improve, really simple ways at that.

Join me next time!