Extending and Refactoring What Matters. Defining Matter.JS Worlds With ClojureScript
Written by Bunkers on March 31, 2017
Previously I wrote a post that was more notes and thoughts I had while I started putting together the kinetic logo project. It's a departure from writing a more traditional tutorial style post, and felt, in some ways, more useful than pasting in sections of code and expecting you to follow along in a step by step way.
I'm continuing in that vain, so any comments you have on whether it's better, worse or meh, let me know!
The code this post refers to is a branch of the logo-walkthrough
repository called `step2-multimethods`
Aha!
Aha moments are coming to me thick and fast at the moment while using Clojure. I've been doing a lot of theoretical learning of Clojure (reading books) and exercises but not so much practical development until recently. Like learning a language by living in a country where it's spoken, there's no substitute for trying to solve your own problems with a programming language.
I look at Clojure code and repetition of code or similar function names start to stand out. I don't always know the best techniques to refactor it, but I at least realise there's optimisation that I could do!
Multi Multi Methods!
That's to the theme of the Power Rangers by the way. Why? Because they're bloody brilliant! Up steps the first of these tools to pop in to my consciousness when I found myself faced with a situation that had me copying and pasting lines of code in my prototype. I wanted to find a more succinct and generic way to prevent that.
In the prototype I had a create-box
function that wrapped the method in Matter.JS to create a rectangular body. It returned a map with the resulting body object and other properties to keep track of.
The logo I'm creating is mainly made up of circles, so I created a create-circle
function. The problem was that this was essentially the same function as create-box
with a slight difference in parameters and the call to Matter.JS. This would be a code smell in any language, but for me it really stands out in Clojure. The inelegance of it makes me want to improve it, like some people are with untidy rooms.
Both of these functions are one liners and as the majority is the same it may be a good candidate for a macro. I avoided this for a number of reasons:
- I've read that you should generally only use macros as a last resort. They're incredibly powerful but in a lot of situations you can get by perfectly well without them.
- There's a limitation with ClojureScript that means you have to define macros in a separate namespace so the Clojure compiler can deal with them. Mix that with an external JavaScript library and it feels like I'm asking for trouble.
- I didn't think of it! Multimethods just appeared to be a good solution, and I still think they are. It's a lot to do with personal preference but also I'd welcome anyone to correct me.
Multimethods are a construct that's similar to method overloading in a language like Java. They allow you to define multiple versions of the same function along with a dispatch function. The dispatch function determines which version of the function gets called for a given set of parameters. This acts like a big if/else or switch statement and allows you to have methods with the same parameters, but different implementations, called based on context.
Neat trick
One particularly satisfying technique is to use a keyword as the dispatch function. Keywords are an alias for the get
function when used as a function themselves, with a map as a parameter. If you define your dispatch function as a keyword, the value for that key in the map passed as a parameter determines the function selected.
That's a difficult concept to explain in words, so here's an example based on my refactoring of the create-box
and create-circle
functions:
(defmulti create-body :type) (defmethod create-body :box [{:keys [x y w h options] :as box }] (assoc box :body (.rectangle Bodies x y w h (clj->js options))) ) (defmethod create-body :circle [{:keys [x y radius options] :as circle }] (assoc circle :body (.circle Bodies x y radius (clj->js options))) ) |
I've replaced the two functions with a new multimethod called create-body
. Having the dispatch function as the keyword :type
means you can call create-body
with a map and the value of :type
determines the method selected.
Defining The World
This has the interesting side effect of allowing us to create a definition of the world using a standard data structure. How idiomatic of us!
You'll notice in the updated code that I've defined a the-world
data structure, and I use msp
to call create-body
on the elements of the vector in setup
. Later I use the same multimethod technique to draw the representations of the bodies.
Becoming Higher Order
Drawing the bodies also had some repeated code. First I move the drawing context to the appropriate x and y coordinates, and then rotate by the body's angle. The only line that differed was the Quil function that actually drew the shape. I therefore created a supporting quil-draw
function that takes a function as a parameter (as I understand it, this would be the definition of a higher order function) and simply calls this at the appropriate place. Doing so feels slick to me, and a natural way of creating reusable code.
Rain it in!
Next I need to look at constraints (joining bodies together) and seeing how I can construct the logo as a complete rig. As always, get in touch if you have any comments or questions.