Casting visual spells with p5.js and ClojureScript, part 1
Tags: clojure, p5js, clojurescript, javascript
Published: 2020-04-22 19:27:55

[p5.js](https://p5js.org) is a JavaScript library for creative coding that lets us bring our ideas into the world quickly, as the example from the [get started page](https://p5js.org/get-started/) shows—you can type it into the [p5.js editor](https://editor.p5js.org/) to see it in action.

```js
function setup() {
  createCanvas(640, 480);
}

function draw() {
  fill(mouseIsPressed ? 0 : 255)
  ellipse(mouseX, mouseY, 80, 80);
}
```

[ClojureScript](https://clojurescript.org/), a dialect of the [Clojure](https://clojure.org/) programming language that compiles to JavaScript, can help us establish a [more immediate connection](https://vimeo.com/109634242) between what we are thinking and the p5.js sketch we are creating.

Let's start by porting the example. To get the most of out this, you can [download the project files](https://github.com/gnandretta/sketch/archive/start.zip) and code along. The [full source code](https://github.com/gnandretta/sketch) is also available.

ClojureScript evaluates lists (things wrapped in parentheses) as calls. For instance, `createCanvas(640 480)` translates to `(js/createCanvas 640 480)`—that is, the literal notation for the list whose first element is the `js/createCanvas` *symbol*, followed by the *numbers* `640` and `480`. We need to prefix `createCanvas` with `js/` to let the compiler know we are referring to the *global* function named `createCanvas`. Every definition in ClojureScript belongs to a namespace (something similar to a JavaScript module) and `js` is a special namespace that lets us use JavaScript variables. For the same reason, we need to set the `setup` and `draw` functions as properties of the `window` object to make them globally available.

```clojure
;; src/sketch/core.cljs

(ns sketch.core) ; all definitions below will belong to to sketch.core namespace

(defn setup [] ; define the setup function
  (js/createCanvas 640 480))

(defn draw [] ; define the draw function
  (js/fill (if js/mouseIsPressed 0 255))
  (js/ellipse js/mouseX js/mouseY 80 80))


;; make setup and draw global functions

(set! (.-setup js/window) setup)
(set! (.-draw js/window) draw)
```

Even though it looks like a regular function call, the `if` expression provided as the only argument of the `js/fill` function is a **special form**. Special forms are the basic building block on top of which the rest of ClojureScript is built and have *special* syntax and evaluation rules. For instance, you only want to evaluate one branch of `if`. `ns` and `set!` are also special forms.

We use `defn` to define functions, which happens to be a **macro** that relies on the `def` and `fn*` special forms. Macros also have particular syntax and evaluation rules but, unlike special forms, we can create our own—and we will later!

So far we've been using p5.js in **global mode**, which is nice for quickly sketching things out but can make things complicated if we want to have multiple sketches. In that scenario, is better to use **instance mode** and create `p5` objects ourselves. The `p5` constructor takes a function that serves as a template for the sketch and an optional DOM element that will contain the sketch.

To ensure the container is in the *document*, we use some functions provided by the `goog.dom` module from Google Closure library. It's available as a ClojureScript namespace with the same name.

```clojure
;; src/sketch/core.cljs

(ns sketch.core
  (:require [goog.dom :as d])) ; require goog.dom, alias to d to save some typing


;; p5.js functions and variables are no longer global but methods and properties
;; of the sketch object p.

(defn setup [p]
  (.createCanvas p 640 480))

(defn draw [p]
  (.fill p (if (.-mouseIsPressed p) 0 255))
  (.ellipse p (.-mouseX p) (.-mouseY p) 80 80))


;; If we can't find an element with the given id, append a new div with that id
;; to the body of the document.

(def parent-id  "example")

(when-not (d/getElement parent-id)
  (d/append js/document.body (d/createDom "div" #js {:id parent-id})))


;; Declare an example sketch by creating a new instance of js/p5.

(def example
  (new js/p5
       (fn [p] ; fn creates an anonymous function, p is the sketch object
         (set! (.-setup p) (fn [] (setup p))) ; attach the setup method to the sketch
         (set! (.-draw p) (fn [] (draw p))))  ; attach the draw method to the sketch
       parent-id))
```

When attaching the `setup` and `draw` methods, we need to create anonymous functions in order to get a reference to the sketch object, named `p` by convention. We could have inlined the definitions of those functions instead.

The code above definitively works but contains a lot of detail. Let's remove some of it and try to get back the convenience of *global mode*. The first step is to identify some tasks that we always have to perform to define a sketch, so we can write some functions to take care of them. We can place those functions in the `sketch.p5` namespace. That way, they won't get intertwined with the code that is concerned with the sketch implementation.

The bodies of the `instance` and `ensure-parent` functions below look quite similar to the original code. The `set-methods` function, however, deserves some explanation.

```clojure
;; src/sketch/p5.cljs

(ns sketch.p5
  (:require [goog.object :as o]
            [goog.dom :as d]))

;; A lot of new things are going on inside the set-methods function, continue
;; reading for the details.

(defn- set-methods [p spec] ; uses defn- to make the function private
  (doseq [[name f] spec]
    (o/set p name (fn [] (f p)))))


(defn instance [methods-spec parent-id]
  (new js/p5
       (fn [p] (set-methods p methods-spec))
       parent-id))

(defn ensure-parent [id]
  (when-not (d/getElement id)
    (d/append js/document.body (d/createDom "div" #js {:id id}))))
``` 

The `set-methods` function expects its second argument `spec` to be a *vector* containing the specification of the methods—a vector is a ClojureScript data structure similar to an *array*. Each method specification is itself also a vector that has two elements: the name of the method and a function with its implementation.

```clojure
[["setup" setup] ["draw" draw]] ; method spec example
```

The `doseq` executes its body for each element of `spec`, binding its contents to `name` and `f` through *destructuring*—a feature analogue to the one present in modern JavaScript. We are using `goog.object/set` instead of `set!` because the property `name` is a string. We don't want to hard-code names since a sketch may use just one of those methods, and may use others.

```clojure
(set-methods p [["setup" setup] ["draw" draw]])

;; executes

(o/set p "setup" (fn [] (setup p)))
(o/set p "draw" (fn [] (draw p)))
```

We can now return to our sketch definition. The code still ensures there's a container and creates a new instance of the `p5` object, but it's not at all concerned on *how* to do that stuff. We can also go ahead and inline the `setup` and `draw` functions since there isn't much going on in their surroundings.

```clojure
;; src/sketch/core.cljs

(ns sketch.core
  (:require [sketch.p5 :as p5]))

(def parent-id  "example")

(p5/ensure-parent parent-id)

(def example
  (p5/instance
   [["setup" (fn [p]
               (.createCanvas p 640 480))]
    ["draw" (fn [p]
              (.fill p (if (.-mouseIsPressed p) 0 255))
              (.ellipse p (.-mouseX p) (.-mouseY p) 80 80))]]
   parent-id))
```

There's nothing particularly special about what we have built until now. Using p5.js instance mode in plain JavaScript looks nicer.

```js
ensureParent("example");

const example = new p5((p) => {
  p.setup = function() {
    p.createCanvas(480 120);
  }
  
  p.draw = function() {
    p.fill(p.mouseIsPressed ? 0 : 255);
    p.ellipse(p.mouseX, p.mouseY, 80, 80);
  }
}, "example");
```

But here's the thing. We can take all these pieces and combine them to define a new language construct `defsketch` that will:

1. Ensure there's a container element in the document.
2. Create a new `p5` instance with the provided implementation and append it to the container.
3. Give the `p5` instance a name.

To extend the syntax of the language, we need to create a *macro*. A macro takes some arguments and uses them to create an expression. That expression is what actually gets evaluated in runtime when you call the macro. Before diving into the implementation of `defsketch`, we need to get familiar with a couple of ClojureScript features.

The `quote` special form suppresses the evaluation of its argument and yields it as it is.

```clojure
(+ 1 1)          ; => 2
(quote (+ 1  1)) ; => (+ 1 1) 
```

The **quote character** `'` provides a shortcut for doing the same thing.

```clojure
'(+ 1 1) ; => (+ 1 1)
```

The **backquote character** `` ` `` works similarly, but it **fully-qualifies** the symbols it encounters—in other words, it adds their namespace.

```clojure
`(+ 1 1) ; => (cljs.core/+ 1 1)
```

Furthermore, inside a *backquoted expression*, the **tilde character** allows to **unquote** some sub-expressions. We can think of *backquoted expressions* as **templates** where *tilde characters* mark **placeholders**.

```clojure
`(+ 1 2 ~(+ 1 2)) ; => (cljs.core/+ 1 2 3)
```

We are all set now, below is the implementation of the `defsketch` macro. Even though the definition of a macro looks like the definition of a function, there are some differences to keep in mind.

- Macros are *applied during compilation* and build the expressions that get invoked at runtime—because the ClojureScript compiler is a Clojure program, macro code is written in files with the *.clj* extension.
- The arguments of a macro are unevaluated ClojureScript code. As we said before, it is composed of ClojureScript data structures—which we can manipulate.

```clojure
;; src/sketch/p5.clj

(ns sketch.p5)

(defmacro defsketch [name methods-spec]
  ;; `let` binds the result of the `(str name)` expression to the `parent-id`
  ;; symbol which we can refer to inside the body of the `let`, kinda similar to
  ;; `let` in js.
  (let [parent-id (str name)]       ; `str` converts its argument to a string
    `(do (ensure-parent ~parent-id) ; `do` evaluates multiple expressions, returns last
         (def ~name (instance ~methods-spec ~parent-id)))))
```

<!-- Because we want to do *two* things (ensure the parent element is in the document and define the sketch) and the macro builds only *one* expression, we use the `do` special form, which evaluates every expression is given in order, and returns the value of the last one. -->

To bring the macro to the `sketch.p5` ClojureScript namespace, we need to add the `:require-macros` option to its `ns` form.

```clojure
;; src/sketch/p5.cljs

(ns sketch.p5
  (:require [goog.object :as o]
            [goog.dom :as d])
  (:require-macros [sketch.p5])) ; because both namespaces have the same name,
                                 ; all macros from the Clojure namespace are now
                                 ; available in the ClojureScript namespace

;; ...
```

We can use the `macroexpand-1` function to see the expression macro is creating.

```clojure
(macroexpand-1 '(p5/defsketch example
                  [["setup" (fn [p]
                              (.createCanvas p 640 480))]
                   ["draw" (fn [p]
                             (.fill p (if (.-mouseIsPressed p) 0 255))
                             (.ellipse p (.-mouseX p) (.-mouseY p) 80 80))]]))

;; results in...

(do (sketch.p5/ensure-parent "example")
    (def example (sketch.p5/instance
                  [["setup" (fn [p]
                              (.createCanvas p 640 480))]
                   ["draw" (fn [p]
                             (.fill p (if (.-mouseIsPressed p) 0 255))
                             (.ellipse p (.-mouseX p) (.-mouseY p) 80 80))]]
                  "example")))
```

It works! The code generated by the macro is not identical to the code we have previously written, but its behavior is equivalent. It would be nice to have a better syntax to define the methods, though. What if, instead of

```clojure
["setup" (fn [p] (.createCanvas p 640 480))]
```

we could write something like

```clojure
(setup [p] (.createCanvas p 640 480)) ; let's call this a "method form"
```

which is idiomatic for macros that expect implementations. Let's try it! We can start by using destructuring to grab the `first` element of the list and gather the `rest` of the elements in another list—the `[first & rest]` binding vector from the `let` form below behaves similarly as a `[left, ...rest]` array placed on the left-hand side of an assignment in JavaScript.

```clojure
(let [[first & rest] '(setup [p] (.createCanvas p 640 480))]
  [first rest])
; => [setup ([p] (.createCanvas p 480 120))]
```

So, now we have to do two things. First, we need to turn the first element of the vector into a string. Then we need to prepend `clojure.core/fn` to the second.

```clojure
(let [[first & rest] '(setup [p] (.createCanvas p 640 480))]
  [(str first) (conj rest 'clojure.core/fn)])
; => ["setup" (clojure.core/fn [p] (.createCanvas p 480 120))]
```

We can transform that into a generic function with more descriptive arguments names than `first` and `rest`.

```clojure
(defn- method-form->method-spec [[name & args-and-body]]
  [(str name) (conj args-and-body 'clojure.core/fn)])
```

Then, the `defsketch` macro can recreate the `methods-spec` vector by applying `method-form->method-spec` to every element of `metod-forms` with the help of the `mapv` function.

```clojure
;; src/sketch/p5.clj

(ns sketch.p5)

(defn- method-form->method-spec [[name & args-and-body]]
  [(str name) (conj args-and-body 'clojure.core/fn)])

(defmacro defsketch [name & method-forms] ; grab every arg after name in method-forms
  (let [parent-id (str name)
        methods-spec (mapv method-form->method-spec method-forms)]
    `(do (ensure-parent ~parent-id)
         (def ~name (instance ~methods-spec ~parent-id)))))

```

Finally, we can write our sketch using the new syntax.

```clojure
;; src/sketch/core.cljs

;; ...

(p5/defsketch example
  (setup [p]
    (.createCanvas p 640 480))
  (draw [p]
    (.fill p (if (.-mouseIsPressed p) 0 255))
    (.ellipse p (.-mouseX p) (.-mouseY p) 80 80)))
```

Wow, that's awesome for less than 40 lines of code! But we don't have to stop there. In the next article, we'll take advantage of other cool ClojureScript features to make our sketch code even more concise.