https://github.com/vvvvalvalval/scope-capture.git
git clone 'https://github.com/vvvvalvalval/scope-capture.git'
(ql:quickload :vvvvalvalval.scope-capture)
[vvvvalvalval/scope-capture "0.3.2"]
This library eases REPL-based development, by providing macros which help you save and restore the local environment of a piece of code with minimal effort.
Project status: beta quality. On the other hand, you typically will only use it in your development environment, so there's little risk in adoption.
This library is designed to support the programming / debugging methodology advocated by Stuart Halloway in this blog post: REPL Debugging: No Stacktrace Required, which consists of:
def
s)What the blog post does not mention is that oftentimes, this first step (recreating the local environment) can get very tedious and error-prone; especially when the values of the environment are difficult to fabricate (HTTP requests, database connections, etc.), which can be the case for online programs such as web servers, or if you don't have a keen knowledge of the project.
scope-capture alleviates this pain by:
sc.api/spy
(which additionally saves the evaluated wrapped expression - or the resulting error, which you can inspect using sc.api/saved-value
) and sc.api/brk
(which acts as a breakpoint, blocking the flow of the program until you choose to release it from the REPL using sc.api/loose!
, possibly with a value which supersedes the evaluation of the wrapped expression using sc.api/loose-with!
or sc.api/loose-with-ex!
)sc.api/defsc
(recreates the environment with global vars, i.e by def
-ing the local names) and sc.api/letsc
(recreates the environment with locals, i.e by let
-ing the local names)As a consequence, to reproduce the runtime context of a code expression, you only need to get it to execute once (not necessarily from the REPL). This makes for:
sc.api/spy
sc.api/letsc
/ with global Vars: sc.api/defsc
/ with a sub-REPL: sc.repl/ep-repl
sc.api/ep-info
, sc.api/cs-info
sc.api/brk
, sc.api/loose
, sc.api/loose-with
, sc.api/loose-with-err
sc.api/undefsc
, sc.api/dispose!
, sc.api/disable!
sc.api/spy-emit
, sc.api/brk-emit
, sc.api.logging/register-cs-logger
sc.api/save-ep
For nREPL integration, see also the scope-capture-nrepl companion library.
With both Leiningen and Boot, it's better not to include scope-capture
in your project dependencies but rather in your local environment. Here's how to do it :
Add the following to the :user
profile in ~/.lein/profiles.clj
:
:dependencies [[vvvvalvalval/scope-capture "0.3.2"]]
:injections [(require 'sc.api)]
Using a $BOOT_HOME/profile.boot
(usually ~/.boot/profile.boot
) file:
(set-env! :dependencies #(conj % '[vvvvalvalval/scope-capture "0.3.2"]))
(require 'sc.api)
(See also the detailed tutorial)
(require 'sc.api)
Assume you need to debug a function with a bunch of locals:
(def my-fn
(let [a 23
b (+ a 3)]
(fn [x y z]
(let [u (inc x)
v (+ y z u)]
(* (+ x u a)
;; Insert a `spy` call in the scope of these locals
(sc.api/spy
(- v b)))
))))
=> #'sc.lab.example/my-fn
When compiling the function, you will see a log like the following:
SPY <-3> /Users/val/projects/scope-capture/lab/sc/lab/example.cljc:52
At Code Site -3, will save scope with locals [a b x y z u v]
Now call the function:
(my-fn 3 4 5)
=> -390
You will see a log like the following:
SPY [7 -3] /Users/val/projects/scope-capture/lab/sc/lab/example.cljc:52
At Execution Point 7 of Code Site -3, saved scope with locals [a b x y z u v]
SPY [7 -3] /Users/val/projects/scope-capture/lab/sc/lab/example.cljc:52
(- v b)
=>
-13
You can now use the letsc
macro to recreate the scope of your spy
call at the previous execution:
(sc.api/letsc 7
[a b u v x y z])
=> [23 26 4 13 3 4 5]
(sc.api/letsc 7
(+ x u a))
=> 30
You can also use defsc
to recreate the scope by def-ing Vars, which is more convenient if you're using the ‘evaluate form in REPL’ command of your editor:
(sc.api/defsc 7)
=> [#'sc.lab.example/a #'sc.lab.example/b #'sc.lab.example/x #'sc.lab.example/y #'sc.lab.example/z #'sc.lab.example/u #'sc.lab.example/v]
a
=> 23
x
=> 3
(+ x z u)
=> 12
If your REPL supports it, you can also achive the same effect by launching a sub-REPL (won't work with nREPL)
(sc.repl/ep-repl 7)
;;;; a, b, u, v etc. will always be in scope from now on
Using scope-capture with ClojureScript is supported, but can get tricky: when in trouble, see the Pitfalls with ClojureScript REPLs Wiki Page.
Dynamic Vars:
spy
/brk
to record dynamic Var bindings, the list of dynamic Vars to observe must be explicitly declared as an option, e.g(sc.api/spy
{:sc/dynamic-vars [*out* my.app/*foo*]}
expr)
sc.api/letsc
will rebind the dynamic Vars that were captured (using (binding [...] ...)
), sc.api/defsc
will not.Local/global name collisions:
sc.api/defsc
will overwrite the global Vars that have the same name as the locals it has captureddef
ed via defonce
, sc.api/defsc
won't work for a local of the same name.For these reasons, using sc.api/letsc
or a sub-REPL is generally more error-proof than using defsc
, although in many cases it is less practical.
Copyright © 2017 Valentin Waeselynck and contributors.
Distributed under the MIT license.