a blog about doing what it says in the title

Week 7 of Hacker School (July 15 to July 21)

2013 July 21

by Richard Harrington

This week I made slow but steady progress on compiling the Robotwar code down to the virtual machine code that came with the original game. I paired with a couple of the facilitators and residents (one — Kevin Lynagh — over Skype) and reworked the code to the point where I liked it, but I spent perhaps too much time refactoring and not enough time moving forward. The question of how much time to spend making one’s code look elegant and perfect is a tough one, and I think I may have been erring too much on the side of endless tinkering, because it’s Hacker School, not a workplace, and no one’s going to stop me. For instance, here are four versions of a parsing function that I created in consultation with a few different people, each of whom had different opinions about how it should be done.

; The parser-priority list is a sequence of pairs. The first of each pair ; is a function which takes a token-string and returns a parsed value, ; e.g. "-6" returns the integer 6. The second of each pair is the associated ; type which will be added onto the token as it moves forward through the ; parsing and compiling process. (def parser-priority
[[registers :register][commands :command][str->int :number][valid-word :label][return-err :error]]); And here are the parsing functions. They all take a token and run it ; through the parser-priority list, running the parsing functions in ; order until they find one that returns a non-nil value, at which point ; they take that value, and the corresponding type from the parser-priority ; list, and use those two things (and the pos value from the original token) ; to return a new token-map:(defn parse-token-original
[{:keys[token-str pos]}](some
(fn[[parser token-type]](when-let[token-val (parser token-str)]{:val token-val, :type token-type, :pos pos}))
parser-priority))(defn parse-token-with-for-comprehension
[{:keys[token-str pos]}](first(for[[parser token-type] parser-priority
:let[token-val (parser token-str)]
:when token-val]{:val token-val, :type token-type, :pos pos})))(defn parse-token-hybrid
[{:keys[token-str pos]}](some identity (for[[parser token-type] parser-priority](when-let[token-val (parser token-str)]{:val token-val, :type token-type, :pos pos}))))(defn parse-token-loop-recur[{:keys[token-str pos]}](loop[[[parser token-type]& tail] parser-priority](if-let[token-val (parser token-str)]{:val token-val, :type token-type, :pos pos}(recur tail))))

The first one uses an anonymous function, the second two use ‘for’ comprehension, and the last one uses loop-recur. I’ve been getting the feeling that directly using the loop-recur structure is less Clojure-y, but I could be totally wrong about that. In some ways it’s the Wild West, and best practices are still being developed.

In the end I think all these versions of the parsing function are fine, with the possible exception of the loop-recur one, because it has no provision for dealing with the case for when you get to the end of the list (is that called the null case?). In fact, it relies upon the fact that the last function in the parser-priority list actually always returns an error string no matter what its input, but this function shouldn’t have to know that.

These are all fine, really. In the end, I think I prefer the one I had in the first place, but only slightly.

As one of the facilitators pointed out, I should arguably not even be reducing this Robotwar source code to its object code, because they’re so similar (see here for an example), and why not just write an interpeter, and have it start doing things already? Like having robots fight? I do wonder sometimes why I’m sticking to the exact form of the original game, but it’s been fun so far, and I’ve also had this idea recently that I want to write a compiler from Scheme down to this Robotwar VM code. We’ll see if I have time for that!