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
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
class_eval, as there are no instances or classes in Clojure. In Ruby,
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
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!