Search code examples
clojurebindingvarread-eval-print-loop

How to see Clojure local variables in local repl?


I want to play around and develop expressions based on local variables by placing a repl (with clojure.main/repl) inside a function body:

(ns something)

(defn myfunc [ p ]
   (let [local (+ p 10)]
        (clojure.main/repl)
        (+ local 100)))

(myfunc 666)

When I executed this, the repl starts ok, but the parameters of the function and local let-bindings do not seem to be visible in the prompt:

something=> p
CompilerException java.lang.RuntimeException: Unable to resolve symbol: p in this context
something=> local
CompilerException java.lang.RuntimeException: Unable to resolve symbol: local in this context

I have been able to pass the values by creating new ^:dynamic vars and setting their values locally with binding, but this is quite complex and requires separate binding for each local variable:

(def ^:dynamic x)

(defn myfunc [ p ]
   (let [local (+ p 10)]
        (binding [x local]
                 (clojure.main/repl))
        (+ local 100)))

Is there simpler way to pass/access local values in such local repl? Or is there some better way to do access the local variables from non-local repl, such as the "lein repl"?


Solution

  • Using the :init hook, you can define arbitrary vars in the REPL namespace.

    (defn myfunc [p]
      (let [local (+ p 10)]
        (clojure.main/repl :init #(do (def p p) (def local local)))
        (+ local 100)))
    

    Here's a repl macro to make adding a breakpoint easier:

    (defmacro locals []
      (into {}
            (map (juxt name identity))
            (keys &env)))
    
    (defn defs [vars]
      (doseq [[k v] vars]
        (eval (list 'def (symbol k) (list 'quote v)))))
    
    (defmacro repl []
      `(let [ls# (locals)]
         (clojure.main/repl :init #(defs ls#))))
    

    Now you can just drop in (repl):

    (defn myfunc [p]
      (let [local (+ p 10)]
        (repl)
        (+ local 100)))