Wednesday, March 4, 2015

Kata: Mars Rover in Clojure using multimethods

Yesterday we had a coding dojo in Clojure Developers Barcelona meetup.

We practiced together doing the Mars Rover kata.

I had already done this kata in Java and C++ before (several times) using the state pattern to eliminate all the conditionals on the rover direction and the command pattern to decouple the translation from the messages sent to the rover into commands acting on it.

As usual I used a mix of TDD and work on the REPL to code this Clojure version.

To document the process I committed the code after every passing test and every refactoring and also committed the REPL history. You can find the commits step by step here.

After making the rover rotations work I had a huge cond on the rover direction with an if branching on the command value inside each case.

It was time to refactor.

The suggestion in the kata was to try to eliminate conditionals using maps, multimethods and/or protocols.

First, I used multimethods, dispatching on the rover direction and the message it was receiving just to see how multimethods work.

It took me a while to make it work because I had a error in the arity of a defmethod and the stack trace was difficult for me to understand.

Then I realized that it was much better to separate the translation of messages into commands from the commands themselves. To do it I used a map that associate each message to the command multimethod:

Here you can see one of the multimethods:

From here on, coding the remaining commands using TDD was very easy. I just had to write a failing test and then make it pass by implementing its corresponding defmethod.

Once all the possible commands were working, I started with the functionality of wrapping the rover in a world.

If you check the commits, you'll see that I tried several approaches here because I wasn't sure how to model the world.

In the end, I decided to model the world as a map with a :wrap-fn key associated to the wrap function for a given kind of world. I passed the world map as a keyword parameter to the receive function which had a default value equal to infinite-world whose :wrap-fn was the identity function. That way all my previous tests continue to work without having to change them.

Then I started doing TDD to code the wrap function for a kind of world called squared-world which was created by a factory function by the same name.

Finally, I started with the obstacles which were modeled as a list of positions (which were represented by maps like {:x 1 :y 2}) associated to the :obstacles key in the world map.

To code the hit-obstacle? function I used the REPL for the first time (you can check all my tests on the REPL history which I also committed) to try to make it work using the some function.

After that I just added a validation to check if the initial position of the rover was on an obstacle.

These are the resulting tests using Midje:

and this is the resulting code:

I'm not sure if this is a good factorization for this problem in Clojure, (I feel that what I did for having different implementations of the wrap function is still a bit OO-ish), but it was a good exercise to practice with multimethods and start exploring their possibilities.

You can find the code in this GitHub repository.

Next time I'll try doing the same exercise using protocols instead.

I enjoyed a lot this coding dojo.

Thanks everyone for coming and Akamon for letting us use its facilities.


Update: I continued working in this code on Separating Mars Rover code into different name spaces, Mars Rover code version using protocols instead of multimethods and Mars Rover using a finite state machine implemented with mutually recursive functions and trampoline

No comments:

Post a Comment