vvvvalvalval.scope-capture

https://github.com/vvvvalvalval/scope-capture.git

git clone 'https://github.com/vvvvalvalval/scope-capture.git'

(ql:quickload :vvvvalvalval.scope-capture)
320

scope-capture

[vvvvalvalval/scope-capture "0.3.2"]

Clojars Project

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.

Demo video:

Demo preview

Talk at Clojure Days 2018:

Talk preview

Rationale

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:

  1. recreating the environment in which a piece of code runs (using defs)
  2. evaluating forms in the code buffer to narrow down the cause of the problem

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:

Benefits

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:

  1. Easier debugging, as you can immediately focus on searching for the cause of the bug
  2. Easier project onboarding, especially for beginners. For someone new to project, and even more so someone new to Clojure, manually fabricating the context of a piece of code at the REPL can be a daunting task, as it requires a relatively comprehensive knowledge of the flow of values through the program. This library lets you do that in a completely mechanical and uninformed way.
  3. Easier exploration. Because it lowers the barrier to experimentation, this library can be also useful for other tasks than debugging and development, such as running one-off queries as variants of existing ones, or just understading how a project works.

Features

For nREPL integration, see also the scope-capture-nrepl companion library.

Installation

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 :

Leiningen

Add the following to the :user profile in ~/.lein/profiles.clj:

:dependencies [[vvvvalvalval/scope-capture "0.3.2"]]
:injections [(require 'sc.api)]

Boot

Using a $BOOT_HOME/profile.boot (usually ~/.boot/profile.boot) file:

(set-env! :dependencies #(conj % '[vvvvalvalval/scope-capture "0.3.2"]))
(require 'sc.api)

Usage

(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

Project goals

Caveats

Using scope-capture with ClojureScript is supported, but can get tricky: when in trouble, see the Pitfalls with ClojureScript REPLs Wiki Page.

Dynamic Vars:

(sc.api/spy 
  {:sc/dynamic-vars [*out* my.app/*foo*]}
  expr)

Local/global name collisions:

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.

License

Copyright © 2017 Valentin Waeselynck and contributors.

Distributed under the MIT license.