Mike Knepper

To Clojure, From Ruby XOXOXOX

April 15, 2014

Last week I started building Tic-Tac-Toe in Clojure. I had done some katas in Clojure a couple weeks prior, but this was my first major project in Clojure, and indeed in any functional language. At the outset I had no idea where to begin—I felt disoriented not having objects and wondered how I’d ever be able to pause and do something like check for a winner on the board, for example. Doesn’t the board need to have some state that I manipulate over the course of the game? However, thanks to a few tips from some other apprentices and recalling lessons from my Ruby TTT, I made progress very quickly, and already have a working game (albeit against a simple AI).

To Data Structures, From Objects

As I mentioned, Clojure does not have objects. Instead, it works directly with data. This was a little hard for me to grasp, being so familiar with Ruby (and maybe a little Java). I kept thinking I needed things like “a board object with an array of spots and an integer size” or “a player object with a token and a reference for how to take a turn.” After talking with Zack and Tom, though, I realized that I can create data structures in Clojure that mimic what I might define as an object in Ruby. The two examples above, for example, can be represented pretty straightforwardly with hash-maps: if a “board” is defined simply by its size and spots, then I don’t really need anything more than {:size 3 :spots [nil nil nil nil nil nil nil nil nil]}. Similarly, an “easy computer player” is just a token (X or O) and a function for playing that token: {:token :x :decision-maker choose-random-spot}. (Side note: because functions are first-class citizens in Clojure, you can pass them around to structures and other functions. In the easy computer hash-map, for example, choose-random-spot is a function that I’m passing around directly. Cool!)

Familiarizing myself with the concept of data structures was the most helpful first step for me. Ruby’s objects started to feel like nothing more than a “shell” or “wrapper” containing data, and in Clojure I was just stripping that wrapper away and working with the data directly.

Return what you need

Another a-ha moment came through recalling a lesson I learned while implementing the MiniMax algorithm in Ruby. In that implementation, I was originally trying to do much in one gigantic method and ran into lots of problems trying to “hold on to too many things,” so to speak. Rylan suggested I extract various parts of my unwieldy method into their own smaller methods. I can remember him asking, “If you need some value from somewhere, why not just write a method that returns it?”

Approaching functions in Clojure this way turned out to be critical. I may not be able to reference some object and change its state like in Ruby, but I can pass data into a function, manipulate it, and return some new data that I’ll then pass on to some other function. At the end of the day, do I need anything else? Programming is about input-process-output. Why bother changing the state of something in the process step when I can just return what I need in the output step, and pass it directly to another function?

Use what you need

Given this flow of data from function to function, we need to also carefully consider what a function requires as input. In Ruby, we can pass entire objects to methods as arguments, though it’s generally frowned upon. Instead, we should only pass in what the method specifically needs and nothing extraneous. This is crucial in Clojure because so many functions get nested as arguments for other functions. As long as we keep our functions focused on small, specific tasks, we’ll be able to pass them as arguments to other functions without worrying about errors or unexpected consequences.

Another area to consider input is in recur loops. As I was refactoring some of my code, I realized a few of my functions were looping “static” data—in other words, in each iteration of the loop, that particular data was being used, but not manipulated. In these cases, there’s no need to include it in the loop’s bindings and the corresponding recur function’s arguments. The only reason to use loop and recur is to continue manipulating data until some condition is met. If some of that data is not changing, it defeats the purpose.

Design and Architecture

I’ve surprised myself with how quickly I’m building my Clojure implementation of TTT compared to my Ruby implementation. Clojure is a brand new language for me, whereas I had been using Ruby for many months when I started that version of the game. I think there are several reasons why I’m going faster this time, such as better comfortability with TDD and Vim, but the biggest factor by far is having an understanding ahead of time of the design and architecture of the application. I knew in advance what kinds of “classes” (or “namespaces”, in Clojure) and functions I would need, so the limiting factor became simply learning the vocabulary of Clojure.

This reminds me of learning Spanish and French back in high school and college. I always felt we spent too much time in class focusing on vocabulary and not enough time on grammar. The way I saw it, there would always be new specific words for me to learn, but teaching myself new words is as easy as looking them up in a translation dictionary. I’d have rather spent more time focusing on grammar, which dictated how to use those words correctly.

By paying close attention to design, architecture, patterns, and organization in projects like my Ruby bank, Ruby TTT, and Java SOLID examples, I’m becoming more comfortable with how to arrange the components of any programming language; learning the specific syntax of those components in a particular language becomes a matter of reading the docs.