Setting up Cursive and Reveal with deps.edn as a beginner

I’m struggling to get all the pieces put together, partly because it’s my first time using deps instead of leiningen, and partly because I’m shaky on the differences between different types of REPLs (clojure.main vs nrepl, for example). The reason for this post is twofold:

  1. To get my own setup working
  2. To document how a Clojure beginner approaches this setup so that maybe some documentation can be tailored towards beginners

I went to the Reveal page, and under Give it a try there’s this configuration for deps.edn:

:reveal {:extra-deps {vlaaad/reveal {:mvn/version "1.3.209"}}
         :ns-default vlaaad.reveal
         :exec-fn repl}

I put this in my ~/.clojure/deps.edn because I thought it would be handy to use in multiple projects. At this point my user-level deps.edn file looks like this:

{
 :deps {org.clojure/clojure {:mvn/version "1.10.3"}}
 :aliases {
           :reveal {:extra-deps {vlaaad/reveal {:mvn/version "1.3.209"}}
                    :ns-default vlaaad.reveal
                    :exec-fn repl}}
}

Next I went to the Cursive integration page, and followed the instructions. At first I didn’t understand, and ended up leaving the “Aliases” field blank and put -m vlaaad/reveal repl in the “Params” field. Eventually I figured out that I was doing something wrong and set up a run configuration like this:

At this point I think I’m ready to go, so I fire up my new “Reveal” run configuration, and try to load my project into the REPL. My directory structure looks like this:

src/
    sketch/
		sketch/
			core.clj
			dynamic.clj
deps.edn

The weird sketch/sketch directory structure is a result of converting this from a Leiningen project and just accepting Cursive’s suggestion for how to move the files around. Not ideal, but not my main concern at the moment.

It’s a pretty simple project so far, here are the file contents:
deps.edn

{:paths ["src/sketch"]
 :deps  {quil/quil {:mvn/version "3.1.0"}}}

core.clj

(ns sketch.core
  (:require [quil.core :as q]
            [sketch.dynamic :as dynamic]))

(q/defsketch the-sketch
  :title "Sketch"
  :size [dynamic/d dynamic/d]
  :setup dynamic/setup
  :draw dynamic/draw
  :settings #(do
               (q/smooth)
               (q/pixel-density 2)))

(defn refresh []
  (use :reload 'sketch.dynamic)
  (.loop the-sketch))

dynamic.clj

(ns sketch.dynamic
  (:require [quil.core :as q]))

(def d 500)
(def win-rad (* 0.99 (/ d 2)))

(defn size
  ([] (size 1.0))
  ([x] (* x d)))

(defn noise-path
  []
  (let [divs 100
        nscale 0.01
        mean-rad (q/random (* 0.1 win-rad) (* 0.95 win-rad))]
    (reduce
      (fn
        [acc div]
        (let [{:keys [last-x last-y]} acc
              noise (q/noise
                      (* nscale last-x)
                      (* nscale last-y))
              max-rad (* 1.25 mean-rad)
              min-rad (* 0.75 mean-rad)
              new-rad (q/map-range noise 0 1 min-rad max-rad)
              angle (* q/TWO-PI (/ div divs))
              new-x (* new-rad (q/cos angle))
              new-y (* new-rad (q/sin angle))]
          (-> acc
              (assoc :last-x new-x)
              (assoc :last-y new-y)
              (update :vertices
                      (fn [vs] (cons [new-x new-y] vs))))
          ))
      {:last-x   (q/random 0 d)
       :last-y   (q/random 0 d)
       :vertices nil}
      (range divs))))

(defn render-path
  [{:keys [vertices]}]
  (let [vs (cons (last vertices)
                 (concat vertices
                         [(first vertices)]))]
    (q/with-translation
      [(/ d 2) (/ d 2)]
      (q/with-rotation
        [
         ;(q/random q/TWO-PI)
         0
         ]
        (q/begin-shape)
        (doseq [[x y] vs]
          (q/curve-vertex x y)
          (if (> (q/random 1) 0.99)
            (do
              (q/no-stroke)
              ;(q/fill 9 26 100 0.5)
              (q/fill 358 34 90 0.75)
              (q/ellipse x y (/ d 50) (/ d 50))
              (q/no-fill)
              (q/stroke 9 26 100 0.3)))))
      (q/end-shape :close))))

(defn setup []
  (q/color-mode :hsb 360 100 100 1.0))

(defn draw []
  (q/no-loop)
  (q/random-seed 0)
  (q/noise-seed 0)
  (q/background 263 11 46)
  (q/fill 9 26 100)
  (q/ellipse (/ d 2) (/ d 2) (/ d 25) (/ d 25))
  (q/stroke-weight (/ d 500))
  (q/stroke 9 26 100 0.5)
  (q/no-fill)
  (doall
    (repeatedly
      1
      #(render-path (noise-path))))
  (q/save "sketch.png"))

When I run this configuration and load core.clj, I see this error and no Reveal window, but the sketch does appear:

(load-file "/Users/zmitchell/code/generative_art/peony/sketch/src/sketch/sketch/core.clj")
Error evaluating - class java.lang.NullPointerException: 
#'sketch.core/refresh

I see the same error when I try loading dynamic.clj, at which point I realize it just hasn’t loaded either file. This error message doesn’t give me any information to go on, so I’m not sure where to go from here.

I found out how to print stack traces with (pst), and this is the stack trace when I load core.clj:

user=> (pst)
Note: The following stack trace applies to the reader or compiler, your code was not executed.
CompilerException Syntax error compiling at (/Users/zmitchell/code/generative_art/peony/sketch/src/sketch/sketch/dynamic.clj:15:18). #:clojure.error{:phase :compile-syntax-check, :line 15, :column 18, :source "/Users/zmitchell/code/generative_art/peony/sketch/src/sketch/sketch/dynamic.clj"}
	clojure.lang.Compiler.analyze (Compiler.java:6812)
	clojure.lang.Compiler.analyze (Compiler.java:6749)
	clojure.lang.Compiler$InvokeExpr.parse (Compiler.java:3824)
	clojure.lang.Compiler.analyzeSeq (Compiler.java:7113)
	clojure.lang.Compiler.analyze (Compiler.java:6793)
	clojure.lang.Compiler.access$300 (Compiler.java:38)
	clojure.lang.Compiler$LetExpr$Parser.parse (Compiler.java:6388)
	clojure.lang.Compiler.analyzeSeq (Compiler.java:7111)
	clojure.lang.Compiler.analyze (Compiler.java:6793)
	clojure.lang.Compiler.analyzeSeq (Compiler.java:7099)
	clojure.lang.Compiler.analyze (Compiler.java:6793)
	clojure.lang.Compiler.analyze (Compiler.java:6749)
Caused by:
RuntimeException No such namespace: q
	clojure.lang.Util.runtimeException (Util.java:221)
	clojure.lang.Compiler.resolveIn (Compiler.java:7388)
	clojure.lang.Compiler.resolve (Compiler.java:7362)
	clojure.lang.Compiler.analyzeSymbol (Compiler.java:7323)
	clojure.lang.Compiler.analyze (Compiler.java:6772)
	clojure.lang.Compiler.analyze (Compiler.java:6749)
nil

So, the problem seems to be that my dynamic.clj namespace doesn’t know about the q namespace that I define in dynamic.clj

Do you add a dependency on Quil somewhere in your deps.edn?

Yes, this is my (project-level) deps.edn:

{:paths ["src/sketch"]
 :deps  {quil/quil {:mvn/version "3.1.0"}}}

Maybe I’d try to start a repl using deps at the command-line, that will at least tell you if it’s your deps or Cursive that is misconfigured

Oh, I also just noticed this code. That code looks janky to me. Normally use will operate over the current value of ns, so if you call this refresh from outside sketch.core, it could unload the currently loaded one due to reload option, but then require it into the namespace you called refresh from, not sketch.core.

I’m not sure, but I’d check what’s happening there, it seems likely to cause issues.

Could you help me with that? I’ve only ever run a REPL from the editor so I don’t know how to debug the interaction between the REPL and my deps file (again, first time using deps).

I removed that and I still get a NullPointerException

If I just go to my home directory and do

$ clj -A:reveal

I get a REPL, but there’s no Reveal window even though my ~/.clojure/deps.edn has an alias for Reveal

{
 :deps {org.clojure/clojure {:mvn/version "1.10.3"}}
 :aliases {
           :reveal {:extra-deps {vlaaad/reveal {:mvn/version "1.3.209"}}
                    :ns-default vlaaad.reveal
                    :exec-fn repl}}
}

It looks like there are issues related to Reveal and Cursive:

Use clj -X:reveal instead.

-X is for “execute function” which is what :exec-fn declares.

-M is for “run main” which is what :main-opts declares. Strictly speaking, -M runs clojure.main and :main-opts are arguments to clojure.main.

-A is intended to start a REPL without running any functions but, for legacy compatibility right now, it still “run(s) main” so it’s essentially the same as -M (but that will stop at some point).

1 Like

If -A is intended to start a REPL without running any functions, what’s the difference between clj -A:some-alias and just calling clj?

Edit: A better phrasing of my question is “what am I missing about -A that makes it different from just dropping into a bare REPL with clj?”

clj on its own will not add anything from aliases. Consider:

{:aliases
 {:test {:extra-paths ["test"]
         :extra-deps {org.clojure/test.check {:mvn/version "1.0.0"}}}}}

Doing just clj will only make the source code of your project available but clj -A:test will make the source code and test code available as well as adding the test.check dependency so you can write and run generative tests in the REPL.

Your Cursive repl config is wrong, the alias should be there without the colon, ie just reveal