Parts 1 through 4 of this series can be found here, here, here and here.
At long last, here is part 5 of my series translating the code examples from w(p)gtr into Clojure! I meant to post this like, 4 months ago, but I’ve been super busy, mostly with music stuff (see my last post for the most recent cool thing I’ve done), and I didn’t get a chance to test this code to make sure it works until just a few days ago. If anyone was really looking forward to this installment, sorry!
Here’s where things really get interesting. _why decides to throw some metaprogramming at us, showcasing an area where Ruby truly shines. Dwemthy’s Array is without a doubt the coolest part of the entire guide. The fact that you can mold and shape the syntax of Ruby to make your own custom DSLs is flat-out inspiring. As it turns out, this is another area in which Clojure (actually, Lisp in general) excels, with its macro system. If I wanted to write an idiomatic implementation of Dwemthy’s Array in Clojure, I would probably actually avoid using metaprogramming and instead use a more functional style and ordinary maps instead of records. But that wouldn’t address the burning question I had going into this translation project, which is essentially, “Can Clojure do everything Ruby can?” So, I took this as an opportunity to compare metaprogramming and classes in Ruby to metaprogramming and records in Clojure.
Also of note, the beginning of this chapter reminded me of how ugly Clojure’s (really Java’s) lower-level methods for dealing with webpage retrieval are, when compared to Ruby’s File
class and open-uri
library. This is something I noticed back in part 2 when dealing specifically with file I/O. Clojure’s spit
and slurp
methods are nice, but in order to read a file (or website content) in line by line, we still have to resort to Java inter-op and deal with explicit reader and writer objects. You’d think there’d be some higher-level construct available in Clojure’s core libraries by now…
It also occurred to me in this chapter that in Clojure, the equivalent of a Ruby block is essentially writing the ~@body
part of a macro. Strictly speaking, you could consider an ordinary function to be the equivalent of a Ruby block (and you’d be right), but in terms of writing a function that takes a block, I think the closest syntactic match would be to write a macro that takes arguments like [foo bar & body]
, where body
is the “block” of code to be executed in some context. I find it interesting the difference in simplicity between Clojure’s (first-class) functions, and the existence of three different things in Ruby (lambdas, blocks and procs, oh my!) that essentially all cover the same territory.
Chapter 6 (Sections 1-3)
The Ruby version of the above example uses the readline
function, which returns one line and essentially “keeps track” of where you are in the file/URL/whatever object so that a subsequent call to readline
will return the next line. Clojure has a similar function called read-line
that works on stream objects, however it’s not idiomatic to the functional programming style that Clojure promotes. The line-seq
function returns a lazy sequence of all lines in a reader object, so it’s more idiomatic to perform sequence operations on a line-seq
. Because the sequence is lazy, it’s no less performant than using read-line
because you still aren’t consuming the entire sequence at once unless you need to do so.
Okay, and we’ve finally reached the infamous Dwemthy’s Array!
This is a great example of the strength of metaprogramming in Ruby. _why concocts a Creature class that contains some sorcery that allows you to, when inheriting the class from another, more specific class such as a Dragon class, utilize a more concise and fun format for representing RPG-style attributes.
Clojure also supports metaprogramming, but goes about it in a different way than Ruby does. In Clojure, you can utilize macros to essentially create any kind of syntax you can imagine.
The first example is just the desired syntax, so we could do something like:
Of course, in _why’s words, “This is not metaprogramming yet. Only the pill. The product of metaprogramming.” This next example is the implementation:
Come to think of it, our Clojure version of _why’s Creature class is actually better in that it’s more flexible! _why’s class is set up such that every class that inherits from Creature has 4 traits: life, strength, charisma and weapon. Our defcreature
macro lets you choose what attributes each creature has. If we wanted to, we could redefine our macro to only allow those 4 specific attributes, and that would even simplify our code a bit. This is a good example of the flexibility, power and (relative) simplicity of Clojure’s macros.
In the next example, _why shows us what Ruby does “behind the scenes” with his Creature class. So, here is what our defcreature
macro does behind the scenes when we call (defcreature Dragon ...)
:
This template isn’t exactly like that of _why’s Creature class. The main difference is that _why’s Creature template defines all creature “subclasses” to have the same 4 traits – life, strength, charisma and weapon. In contrast, our defcreature
macro lets each creature have its own custom traits, making for more flexible creature creation. This is certainly possible in Ruby, as well, but would perhaps make for a more complicated example than would be digestible in _why’s introduction to metaprogramming in Ruby.
Clojure has an eval
method too, but it operates on a data structure instead of a string:
Clojure has no equivalent to instance_eval
and class_eval
, as there are no instances or classes in Clojure. In Ruby, instance_eval
and class_eval
are useful for adding functionality to instances and classes “on the fly,” e.g. within an irb session, or perhaps even conditionally within a program – allowing you to do things like modify a character class in a certain way if a player reaches a certain point or does a certain thing. As _why puts it, this ability of code to modify code dynamically at runtime “can be useful and … can be dangerous as well.”
At this point in translating Dwemthy’s Array, we need to implement the hit
and fight
methods. We could do this in a functional style, as part of a “Battle” protocol that would be a part of the defrecord
generated by our defcreature
macro. However, records don’t work too well with mutable state in Clojure, since a record is supposed to be an immutable representation of the state of an “object” at any given time.
While it would certainly be possible to re-do Dwemthy’s Array from scratch in a functional style, this would change the dynamic of the original game in Ruby. Dwemthy’s Array is interesting in that _why designed it to be played in an “open” fashion from irb – you might even call it a “metagame.” The player is the programmer. You create an instance of a creature like the Rabbit, define a “Dwemthy’s Array” of enemy creatures, and then have your creature make the first move on the Array, which starts a chain reaction of battling each creature of the Array in succession.
So, for a faithful translation of this game in the spirit of the original, we would want it to be playable in a REPL environment. While it is possible to implement the necessary methods in a functional style, the object-oriented nature of this kind of game lends itself towards using mutable state via Clojure’s reference types.
Whereas the original Dwemthy’s Array relies on monkey-patching (via method_missing
) to allow the Rabbit to attack the Array directly, to make things cleaner we are implementing this as an ordinary function called attack
, which takes a challenger (e.g. the Rabbit), an attack function (e.g. the bomb *
), and a Dwemthy’s Array as arguments. It’s a little more to type, but it makes for safer and tidier code. (You can chalk this one up as a victory for Ruby if you value aesthetics over safety!) ;)
How would you have done Dwemthy’s Array differently? Comments welcome!