I’m a Ruby programmer trying to learn Clojure and functional programming by making a Rubik’s cube in code, making mistakes as I go. In the last post I made a data structure, which is a 26-element vector of maps representing the little cubes that comprise the Rubik’s cube. Each one contains information about its position and the colours of its faces. Read more about the data structure here.
I need a way to manipulate the cube. And a way to look at it too, because if I can’t see what’s on each side, there’s no way I’m going to know if the various transformations we do to it are working. I decided to name the sides of my cube North, South, East and West, and the top and bottom faces Up and Down. I deliberately didn’t follow the convention of Left, Right, Front and Back because I didn’t want to have to deal with relative positions when I was transforming the cube. This turned out to be a slightly overcomplicated way of doing things, because I spent quite a lot of effort translating NSEWUD into x, y and z.
Anyway, I ended up with the following functions. You can find their implementations on GitHub. I didn’t consciously design anything up front. My process was more like
- take stab at implementing the whole operation (reading the faces out of a side)
- refactor that function into smaller functions that are easier to test
For example, the first function I wrote was called ‘get-face’. During this process it delegated a few things down into other functions, and it also lost some responsibilities, which I pushed upwards into functions which called it. Having imagined it would be my public interface, in fact I ended up with it somewhere in the middle of the chain of functions required to get the colours of a face.
;; take a cube and a face and return an array of colours in a predictable order. (get-face-colours (get-sorted-face face cube)) ;; infer which aspect (x, y, or z) a set of pieces share. ;; For this function to return x, for example, all ;; the pieces must have colours in the x axis, though ;; some of them might also have colours in the y or z axis. (get-aspect pieces) ;; sort a collection of pieces into a consistent order, ;; taking into account which plane it's in (x, y or z). ;; This function is 17 lines long (!) and consists mostly ;; of a huge (case) statement. (get-sorted-face face cube) ;; this function exists only to execute the various permutations ;; of operators and planes from get-sorted-face using sort-by (sort-pieces-by-axis plane operator pieces)
As you can see, concerns fell out while I was writing this and they became other functions. This process is not very different from writing a program in Ruby, though obviously without a class to wrap these things up there is no state to lean on so there are a few more params flying around. In that respect I feel like I am programming in a more functional style.
Am I happy with these functions? Sort of.
BIG TICK: They are all pure functions
SMALLER, MORE AMBIVALENT TICK: It’s possible to test each one of them. However, because they pass my cube and piece objects between themselves, their tests all require fully-fledged data structures. This makes them feel not-very-reusable. They all need to know about a map I defined elsewhere called
face-mapping, which translates :N, :S, :E, :W etc into an internal map the contains some information about that face, like which axis it’s in and how to filter out its pieces from the cube.
NO TICK: Most of them are 2 or 3 lines long, except
get-sorted-face which is a 17-line monster. This is probably because get-sorted-face knows a lot about how I want my pieces sorted when I ask for them in a face. It feels like it would be nice to substitute this with something a bit more generalised, and then my code would not have to worry about my data structure.
This is something that I haven’t experienced before. With object-oriented programming, there is rarely the opportunity to decouple the implementation of the program from the data structure, because the bit of the program that’s concerned with the data lives with that data. Although my functions live on the wide open prairies of a Clojure namespace, they won’t be much use to anyone who meets them out there.
I revisited the last 10 minutes of ‘Simple Made Easy’, where I heard this:
[Objects] were never meant to be applied to information… It’s wrong. But I can now say it’s wrong for a reason. It’s wrong because it’s complex. In particular, it ruins your ability to build generic data manipulation things. If you leave data alone, you can build things once that manipulate data, and you can reuse them all over the place, and you know they’re right once and you’re done
Between us, I’m not sure whether striving for generic functions might actually be better than just chiselling out functions that accommodate the data a la 99 bottles, but I’m here to learn so I’m going to try it. So: two concrete lessons I’ll take from this:
- Instead of enabling my functions to work on complex data, I should try to make my data simpler so my functions can be too.
- I could probably find a library to provide those basic functions for me. So the only thing I would have to do would be get my data into shape.
Next post: graphics!