Exploring using (def x x)

I find myself using def in the middle of functions for exploring what is happening inside them, or what inputs they get. Here’s a somewhat (but not fully) contrieved example. I am trying to hook a React Native project on re-frame, and I have never used re-frame before. To learn what arguments are sent to the events, I use def to be able to use the REPL inside my editor to explore the inputs. The classic example is increasing a click-counter and this is my :inc-counter event:

image

It works, but before it worked I had to figure things out and instrumented the function like so:

image

Then, after clicking my Click me button. I could evaluate the forms and learn about what I was dealing with, like so:

As a beginner in Clojure land, I wonder if this is a ”dirty” way to do it and if I can expect problems doing things like this. It almost feels too easy, and I am trained by life to suspect too easy means I have to pay later in some way.

Some call it dirty, I call it effective and do it all the time. Just remove the defs when you’re done.

https://blog.michielborkent.nl/2017/05/25/inline-def-debugging/

1 Like

Ah, wonderful! The REPL in Clojure is really something completely else than the REPLs you find in other languages.

1 Like

This is what new tap> fn is for right?

(add-tap println)
=> nil
(defn silly [x]
  (let [y (inc x)
        z (dec x)]
    (tap> z)
    (* y z)))
=> #'user/silly
(silly 8)
=> 63
7
2 Likes

Cool! That works for some use cases, and is certainly a good tool.

However, it doesn’t help very much with interactively finding out the right body of code that will solve a task inside a function. By deffing the inputs like that I can build the function using the REPL from inside the function. A strong complement to using the REPL for testing the function from the outside.

Yes, it litters down the namespace in the running session, and that has bitten me at times. Tradeoffs, tradeoffs. :smile:

Some time ago I’ve stumbled upon the support library for this debugging technique: scope-capture. There is even a decent demo video. Back then it seemed really impressive, but I hadn’t have an opportunity to try it myself.

There is also another lib with a similar name: spy-scope. Among others it has a feature, similar to recently announced tap>.

2 Likes

I like to use the spy function from timbre, which logs its argument along with its name, and returns its argument. So you can use it to wrap any value or expression that you’re curious about and see it logged at runtime.

Admittedly that’s less interactive than your approach with def, but it has the advantage that you can keep such statements (within reason) in production code and just adjust the log level accordingly.

So you could do something like:

(require '[taoensso.timbre :as log])

(fn [db [x v]
  (log/spy db)
  (log/spy x)
  (log/spy v)
  (log/spy (:counter db))
  (log/spy (assoc db :counter (log/spy (inc v)))))

Not sure if this is better than your approach or not, it’s just the way I do it :slight_smile:

1 Like

I’d say it has its uses and beats println. Now that I know about it, I will definitely put it to use.

Wow, yes, it really is an automation of the technique! I guess that answers my question. :smile:

The video perfectly shows how the REPL power is leveraged. I think I should add support for scope-capture in Calva somehow.

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.