Anatomy of a Clojure function

Codeidoscope
Sep 3, 2018 · 7 min read

When I first started learning functional programming, one of the things that baffled me the most of Clojure — my language of choice — was how to use functions within functions.

If you have never touched Clojure or functional programming, then you may not know that data is immutable. This means that you cannot store it into a neat little variable and reuse that variable later. At first, I found the concept a little mind-blowing. How on earth was I going to do anything?!

It turns out you just need to think about the bigger picture a little earlier on when programming functionally. You may be working on a tiny piece of code, but that code will return a result, and that result will be directly used by another function that will manipulate it to return something else and so on and so forth.

I’m still working on my Tic Tac Toe game, so let’s look at a little problem I solved last week. Until last Friday, this is what my Tic Tac Toe looked like when I ran my programme on the command line:

“Erm, ok, that’s a bit bare” I hear you say. You’d be right. This is one of the very first function I implemented when I started my programme, and I was then only concerned about having three lines. Let’s look at the code that returned this piece of art (/s):

Don’t be daunted by the brackets, we’re going to peel it apart in a second. First of all, let’s look at line 1. It’s simple and to the point, I tell Clojure I want to create a function by using defn ( def would create something close to a variable, which you could use to display a prompt every time the player took a turn, for example.) You can make it a private function by using defn- and there’s other fun things that I won’t look at in more details in this article.

format-board is just the name of my function. It’s simple and to the point, it tells you what the function does and that’s it. In-between square brackets, we have the [board] argument, which is a vector that looks something like this ["0" "1" "2" "3" "4" "5" "6" "7" "8"] or something like [“X” “_” “O” “_” “X” “_” “X” “_” “O”].

Now let’s dig deeper into line 2, where the fun really starts. Clojure “nests” are read from right to left: the right-most function is the first one to be executed. In this case, this is the order in which things are happening:

  • Step 1: (apply str board) — This turns my vector into a string by applying the str built-in function to my argument board. This returns a string, like “012345678” or “X_O_X_X_O”.
  • Step 2: (re-seq #".{1,3}" (apply str board)) — There definitely was a little copy-pasta going on, but what is happening here is that the result of the function we ran in our first step (the string) is now being split at every three characters using re-seq which takes a regex pattern (here it was #".{1,3}"), and a string, which was our “stringified” board. There are nine characters, so we get three little chunks in total, and this function returns a list of string: (“X_O” “_X_” “X_O”).
  • Step 3: (string/join “\n” (re-seq #”.{1,3}” (apply str board))) — Let’s focus on the third function I’m using here. I’m trying to join my three chunks again, but this time I want to insert a newline in between so they are displayed nicely (lol) on my terminal. To do this, I used the clojure.string/join function which sticks back the bits of the string I’ve been manipulating in steps 1 and 2 together with a new \n character in-between. This function returns this little beauty: “X_O\n_X_\nX_O”.
  • Step 4: (println (string/join “\n” (re-seq #”.{1,3}” (apply str board)))) — Still with me? Thankfully, here we’re just printing the whole thing and it looks like this:
X_O
_X_
X_O
nil

Wait, why on earth is that cheeky nil sticking to the end?! My assumption is that a function has to return something in Clojure, and when it doesn’t really return anything (in this case, for example, it’s only printing, rather than returning), then it returns nil. Let’s look at the next step to fix this problem!

  • Step 5: (with-out-str (println (string/join “\n” (re-seq #”.{1,3}” (apply str board))))) — With with-out-str, we're just removing the nil that wanted to stick to the end of my print statement so that it looks a little nicer on the command line. This step returns this “X_O\n_X_\nX_O\n”, which looks like the code I posted above in formatted-board when printed.

So what happened? I created some data in step 1 that I passed as an argument to the function I used in step 2. It’s itself passed as an argument to the function used in step 3, which is then passed as an argument to the function used in step 4, and that final result is passed as an argument to the function is step 5, which returns something completely different to what I started with in step 1.

What changed? Well, I cannot ever access the data from step 1 ever again. It was created, and modified pretty much on the fly, but it is not stored at any steps of the way. By running this format-board function, I am only ever able to retrieve the data from step 5, but can never access the data that is passed to it before it reaches step 5. And that’s the beauty of functional programming.

At first, I was confused by it because I thought it might make it more complex. And in a way, it does because I have to think about my code and be sure of what I want it to do before I implement anything. Clojure does not like my ugly code and will not forgive me for being sloppy. And that’s a good thing. Because I’m forced to think about my data in more details and how to pipeline my functions, it also means that I spot where bugs and issues come from a lot faster. Why? Because there’s usually only one place where a specific function is called, so I can isolate my bugs faster and fix them more quickly (I bet I jinxed myself now…)

“Ok, that’s cool, but your terminal output is still ugly af girl, how are you gonna fix that?” Yeah, you’re right, it’s not very clear what it represents and I can do better. And I did! On Friday I spent some time thinking about how I could avoid manipulating a string and just use my vector first before making it a string. Now, my board looks like this:

And how did I get to this? Simples! I rewrote my function and turned it into this:

Line 1 hasn’t changed, but here’s what’s happening. I can’t use steps as neatly as before, because I’m using iteration, but let’s snoop around anyway. There are roughly three main components to my function:

  • Component 1: (for [part (partition 3 board)] (flatten (map vector part ‘(“ | “ “ | “ “\n — — — — -\n”))))
    For each of the parts created from (partition 3 board), map the part of the board with “ | “ “ | “ “\n — — — — -\n” — this effectively zips the two elements (just like your clothes’ zippers) and it inserts one element after another. If you remember my board from above (this one [“X” “_” “O” “_” “X” “_” “X” “_” “O”]), this is what my first line now looks like: (“X” “ | “ “_” “ | “ “O” “\n — — — — -\n”). As you can see, it's taken the first three elements of my board vector, and created a new vector by linking the first element of my partition( “X”) with the first element of my unnamed vector (“ | “) and same for the rest of the elements. I flatten each of the partition so that I don’t end up with a vector that contains a bunch of vectors that will confuse me, like this unflattened monster:
(([“X” “ | “] [“_” “ | “] [“O” “\n — — — — -\n”]) 
([“_” “ | “] [“X” “ | “] [“_” “\n — — — — -\n”])
([“X” “ | “] [“_” “ | “] [“O” “\n — — — — -\n”]))

(Yeah, ew, right?)

  • Component 2: (flatten (for [part (partition 3 board)] (flatten (map vector part ‘(“ | “ “ | “ “\n — — — — -\n”)))))— We’re just flattening the less monstrous result of our iteration from this:
(("X" " | " "_" " | " "O" "\n---------\n") 
("_" " | " "X" " | " "_" "\n---------\n")
("X" " | " "_" " | " "O" "\n---------\n"))

To this:

("X" " | " "_" " | " "O" "\n---------\n" 
"_" " | " "X" " | " "_" "\n---------\n"
"X" " | " "_" " | " "O" "\n---------\n")
  • Component 3: (apply str (flatten (for [part (partition 3 board)] (flatten (map vector part ‘(“ | “ “ | “ “\n — — — — -\n”))))))— And here we’re turning the whole thing into a string so that it can be nicely printed by another function.

Now I have a board that looks like board, and hopefully you’ve learnt something about the flow of a function in Clojure!

Codeidoscope
Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade