GithubHelp home page GithubHelp logo

vvvvalvalval / scope-capture Goto Github PK

View Code? Open in Web Editor NEW
557.0 13.0 13.0 87 KB

Project your Clojure(Script) REPL into the same context as your code when it ran

License: MIT License

Clojure 100.00%
clojure repl debugging tooling clojurescript

scope-capture's Introduction

scope-capture

[vvvvalvalval/scope-capture "0.3.3"]

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:

  • providing macros that let you save snapshots of the local environment on the fly: 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!)
  • providing macros that let you restore these snapshots from the REPL: 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)

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

  • Recording / logging the runtime scope and evaluation of an expression: sc.api/spy
  • Re-creating a recorded scope: with let-bound locals: sc.api/letsc / with global Vars: sc.api/defsc/ with a sub-REPL: sc.repl/ep-repl
  • Accessing recorded information: sc.api/ep-info, sc.api/cs-info
  • Suspending and resuming execution (similar to breakpoints): sc.api/brk, sc.api/loose, sc.api/loose-with, sc.api/loose-with-err
  • Cleaning up after yourself: sc.api/undefsc, sc.api/dispose!, sc.api/disable!
  • Customizing: sc.api/spy-emit, sc.api/brk-emit, sc.api.logging/register-cs-logger
  • Artificially creating scopes: sc.api/save-ep

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.3"]]
: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)

Docs and API docs

cljdoc badge

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 

Perhaps surprisingly, defsc is the author's preferred way of recreating the scope. It's usually not as dirty or dangerous as it looks.

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

ClojureScript usage

IMPORTANT: In a typical ClojureScript environment, you have to write (sc.api/letsc [7 -3] ...), not (sc.api/letsc 7 ...). Same thing for sc.api/defsc. Writing (sc.api/letsc [7 -3] ...) is also possible in JVM Clojure, just not mandatory.

How come? This is because in ClojureScript, the information linking the Execution Point 7 to the Code Site -3 is not available to the compilation process in charge of macro-expanding letsc and defsc. That's why we have to provide the Code Site ID (-3) explicitly, as a hardcoded number. To achieve a better understanding of this, see the Pitfalls with ClojureScript REPLs Wiki Page.

Project goals

  • Providing practical ways of recreating the runtime environment of a piece of code at the REPL
  • Targeting a wide range of Clojure execution platforms
  • Providing a good foundation for additional tooling, e.g editor integrations
  • Being well documented, and friendly to beginners
  • Being customizable.

Caveats

Using scope-capture with ClojureScript is supported, but can get tricky: don't forget the above guideline about ClojureScript usage, and when in trouble, see the Pitfalls with ClojureScript REPLs Wiki Page.

Dynamic Vars:

  • In order for 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 captured
  • In particular, if a global Var has been defed 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.

Related tools

Dear tooling authors, if I failed to mention any of your tools, please submit a PR!

Similar

There are other Clojure tools that enable you to recreate the local context in your REPL:

Complementary

scope-capture has a narrow focus: putting you in the shoes of your program through your REPL. It does not address other debugging concerns, such as visualizing data or observing execution: this can be achieved by complementing this library with other tools, such as tracing libraries, data visualization UIs, pretty-printers, etc. See the Clojure REPL guide for an overview of REPL tools and techniques.

Analogous

Here are tools that achieve a similar effect in other languages:

  • Pry (Ruby)
  • ipdb (Python)
  • the Node.js Debugger offers a repl command to evaluate code at a breakpoint. (JavaScript - Node)

License

Copyright © 2017-2020 Valentin Waeselynck and contributors.

Distributed under the MIT license.

scope-capture's People

Contributors

adamfrey avatar blak3mill3r avatar chpill avatar djebbz avatar holyjak avatar vvvvalvalval avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

scope-capture's Issues

Non intrusive support for naming code sites: multi methods

The following 4 code changes have allowed me to do something like (sc.api/defsc :brk-1) in ClojureScript, which pages in the scope prevailing at the last call to (sc.api/spy {:sc/cs-label :brk-1} ...). I argue that the proposed code changes do not alter the behaviour of scope-capture in any way, as they are basically only switching functions to multimethods.

I did three things in sc.impl:

  1. in the map of function make-cs-data I added a key-value pair :sc.cs/label (get opts :sc/cs-label nil)
  2. out of function find-ep I made a (defmulti find-ep #(type %2)) (plus defmethod :default)
  3. out of function resolve-code-site I also made a multimethod (dispatch function type)

In sc.api I
4 . removed the line ep-id (i/resolve-ep-id ep-id) from macro defsc.
This line is rudundant, as the check (validate-ep-identifier ep-id) is triggered by the immediate above call to our (default) resolve-code-site, and the subsequent call to our find-ep does resolve-ep-id ep-id on its own.

With additional definitions of find-ep and resolve-code-site multimethods in my own codebase (here and here), I am able to introduce breakpoints in Clojurescript in the sense described at the beginning.

How should i get the execution point and code site without using logs?

As the title says, i would like to get the execution point and code site without going to the logs. My logs are often very cluttered.

I would like the option to both just view it, maybe in a global atom. AND turn on a mode where the spy information is just up to date with the last eval. I'm curious if you have any feedback on either of these ideas, especially around pitfalls. Thanks for such a great tool!

Scope-capture doesn't play nice when used in core.async's go-block

Hello,

I only use letsc and defsc, and noticed that when some code is spyed inside a go block from core.async, defsc doesn't work and fails with this strange error :

(sc.api/defsc 15)
             java.lang.RuntimeException: Unable to resolve symbol: objects in this context
clojure.lang.Compiler$CompilerException: java.lang.RuntimeException: Unable to resolve symbol: objects in this context, compiling:(/tmp/boot.user5571589663493149098.clj:1:1)

letsc works fine. I suppose it's because of the big code rewriting that core.async does. I'm not sure if it's a bug. Just reporting.

Error in tutorial

Hi Val,

Just wanted to point out what looks like an error in the tutorial:
[EDIT - I originally wrote README by mistake, but I mean this section of the tutorial. ]

You can disable the side-effects of the spy and brk macros at a given Code Site by calling sc.api/disable!

(sc.api/dispose! -2)

I assume you meant the example to use disable! rather than dispose!.

Thanks for scope-capture, terrific tool!

Working with shadow-cljs?

I am running shadow-cljs and as soon as I require sc.api I get this error:

------ WARNING #1 --------------------------------------------------------------
 File: sc/impl.cljc:52:25
--------------------------------------------------------------------------------
  49 |                "ep-id should be either a positive number or a [(positive-number) (negative-number)] tuple, got: "
  50 |                (try
  51 |                  (pr-str ep-id)
  52 |                  (catch Throwable err
-------------------------------^------------------------------------------------
 Use of undeclared Var sc.impl/Throwable
--------------------------------------------------------------------------------
  53 |                    "(failed to print)")))
  54 |              {:sc.api.error/error-type
  55 |               :sc.api.error-types/invalid-ep-id
  56 |               :ep-id ep-id}))))
--------------------------------------------------------------------------------

My require statement looks like this:

(ns permits.classify
  (:require
   [clojure.string :as string]
   [sc.api]))

My shadow-cljs.edn dependencies are:

 :dependencies [[reagent "0.7.0"]
                [re-frame "0.10.5"]
                [binaryage/devtools "0.9.10"]
                [day8.re-frame/re-frame-10x "0.3.2-react16"]
                [day8.re-frame/tracing "0.5.1"]
                [vvvvalvalval/scope-capture "0.3.1"]
                [bidi "2.1.3"]
                [kibu/pushy "0.3.8"]
                [cider/cider-nrepl "0.17.0"]
                [soda-ash "0.81.1"]]

I get the error before even trying to use spy in the code. I know it's hard to make this work with all repl's. Has anyone successfully made this library work with shadow-cljs? It's possible it has nothing to do with shadow-cljs, but then what could it be?

Thanks

Trouble with cljs

Hi val!

I've been trying to use this library (finally!), but I've encountered some issues with clojurescript. The page on the pitfalls of cljs repls states that using the REPL provided by figwheel should be okay. Unfortunately I could not get it to work. I've set up a minimal case to show the issue I encountered here https://github.com/chpill/cljs-sc-test.

TL;DR I end up having those seemingly contradictory results at the REPL:

dev:cljs.user=> (require '[sc.api :as sc])
nil
dev:cljs.user=> (sc/ep-info 1)
#:sc.ep{:id 1,
        :code-site
        #:sc.cs{:id -1,
                :expr (+ a b c),
                :local-names [x a b c],
                :dynamic-var-names nil,
                :file
                "/home/chpill/p/clj/buf/cljs-sc-test/script/cljs-vanilla-repl.clj",
                :line 10,
                :column 5},
        :local-bindings {x 4, a 8, b 12, c 16},
        :dynamic-var-bindings {}, 
        :value 36}
dev:cljs.user=> (sc/letsc 1 a)
----  Could not Analyze  <cljs form>   line:1  column:1  ----

  No Execution Point with ID 1

  1  (sc/letsc 1 a)
     ^--- 

----  Analysis Error  ----
nil

Any idea what might be going wrong?

Document ClojureScript browser-REPL pitfalls

Bad cases:

  1. When several compilation processes send code to the same runtime (e.g file watcher + browser-connected REPL)
  2. When one compilation process sends code to several runtimes -> beware of which runtime your REPL is hooked to
  3. Compilation caching

ClojureScript: compile-time logs mixed with JS code in output

Seems like scope-capture would allow more convenient repl based development for my CLJS web apps. Can't see technical reasons it can't be made to work. (That would be exciting)

I gave it a try, there's currently a problem with how the repl output is generated. Print statements end up on the JS code which is compiled causing errors.

For example:

var CODE = cljs.core.get.call(null,map__68496__$1,new cljs.core.Keyword(null,"CODE","CODE",-1885949257));
var TITLE = cljs.core.get.call(null,map__68496__$1,new cljs.core.Keyword(null,"TITLE","TITLE",-120772486));
var link_to_project = (cljs.core.truth_(PROJ_PROJECT)?utas.routing.path_for_BANG_.call(null,new cljs.core.Keyword("pmr.project",...
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"div","div",1057191632),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, ...;
});
SPY <-5> figwheel.clj:119 
  At Code Site -5, will save scope with locals [show-alloc? user show-wrf? props WR map__68500 insurance-program? p__68499 map__68502 comment-form]
wractioning_ui.views.connect_views.WRDetailsPage = (function wractioning_ui$views$connect_views$WRDetailsPage(p__68499){
var map__68500 = p__68499;
var map__68500__$1 = ((((!((map__68500 == null)))?((((map__68500.cljs$lang$protocol_mask$partition0$ & (64))) || ((cljs.core.PROTOCOL_SENTINEL === map__68500.cljs$core$ISeq$)))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__68500):map__68500);
var props = map__68500__$1;

Perhaps this can be avoided with a new register-cs-logger?

Alternate spy with less noisy defaults

Sometimes the default logger for spy and brk is unwieldy, especially when dealing with large / costly to print value. It could be helpful to provide an alternate spy (spysl, for "spy silent" maybe) to solve that problem

Support for naming code sites

First of all, awesome library that has been incredibly useful! Thanks for all of yall's work

I was thinking that a very useful feature would be to provide names (keywords, most likely) for code sites. Combined with something like last-ep-id this would give you the ability to refer to & debug arbitrary points in your code very easily and without the user having to fiddle with the integer arguments.

My particular motivation is that I have a series of transformations in a big ->> which I scatter with (#(do (def some-name %) %))s. I then shove the captured data into https://github.com/lambdaisland/deep-diff to very clearly see what each transformation step does.

Using spyqt & ep-value for this seems like a no brainer, except for the somewhat tedious cross referencing required to get the call site ids. FWIW this may be useful for #34 in that they could search for the most recent execution id for a specific call site more easily.

Happy to take a crack at it myself in a PR if you'd like to give me your 2 cents about this. Obviously the names would no longer be unique as the integer ids are, and I dont know if this would be an issue for the library.

Use in production?

What is your recommendation about using scope-capture in production so that we could connect to a nREPL (e.g. with lein) and inspect our code?

Is there a way to dynamically load scope-capture ?
Is it safe to include scope-capture to our code base ?

Not getting local-bindings as expected.

I followed the instructions in the detailed tutorial and i'm not getting the local bindings.


(sc.api/ep-info 8)
;; => {:sc.ep/code-site {:sc.cs/column 9,
;;                       :sc.cs/dynamic-var-names nil,
;;                       :sc.cs/expr (*
;;                                    2
;;                                    earth-radius
;;                                    (.
;;                                     Math
;;                                     asin
;;                                     (.
;;                                      Math
;;                                      sqrt
;;                                      (+
;;                                       (haversine (- phi2 phi1))
;;                                       (*
;;                                        (. Math cos phi1)
;;                                        (. Math cos phi2)
;;                                        (haversine (- lambda2 lambda1))))))),
;;                       :sc.cs/file "/home/drewverlee/aws/playground/src/drewverlee/aws/playground.clj",
;;                       :sc.cs/id -12,
;;                       :sc.cs/line 143,
;;                       :sc.cs/local-names []},
;;     :sc.ep/dynamic-var-bindings {},
;;     :sc.ep/id 8,
;;     :sc.ep/local-bindings {},
;;     :sc.ep/value 0.0}

What can i check? what you help you help me here? I'm on the latest version, using deps.

Cool discovery: you can nest `letsc`!

Hi,

I've just discovered that when one captures with spy at different places of the code, even if they're executed at different moments (imagine 2 different HTTP requests to the same server), you can nest the letsc to access both contexts at the same time! Ex:

(letsc 1 ;; id of the first capture, for the first HTTP request
  (letsc 2 ;; if of the 2nd
    [res1 res2])) ;; I can access both HTTP responses (provided the names of the vars don't clash I suppose)

It works like nesting normal lets. I have no idea if it was obvious to you, but for me this discovery makes scope-capture even better-er. Should it be documented somehow? Only stating the possibility is enough, like "You can nest letsc like you nest let".

Document internals

The reader should understand:

  • what information is created and accessed by what processes, and where it's stored.
  • how spy / letsc / defsc work

Capturing values in thread first or thread last statements

It would be nice to be able to capture each step in threading macros. For example:

(defn abbr-into-set
  [permit keyword-map]
  (sc/spy
   (into #{} (->> keyword-map
                  (filter #(include-exclude-match? permit %))
                  (map first)
                  (map name)))))

I could capture the intermediate state for each step in this thread last macro. When I tried this spy only captured the function arguments. I couldn't see a way to capture the rest in the documentation. Do you think this would be a good feature?

Disable prn of captured values at execution time

It would be useful for spy not to prn the results of captured values at execution time.

The problem I have is that some of the values I have in scope are massive, and spy's printing of them can fill my repl with MBs of generated text. Emacs then tries to syntax highlight this which causes it to slow down until I manage to get it to run M-x cider-repl-clear-buffer

It would be useful when dealing with larger data for spy to either disable the prn of values completely, or provide a new variant of spy that doesn't print at all, and leaves the captured values for you to evaluate later.

Capture scope only on exception

I've noticed that "spying" on an expression incurs a significant overhead when used in tight inner loops that undergo many iterations.

Most of the time I want to capture the local scope and be able to recreate it later, only when something exceptional happens. Constantly overwriting the same code site and logging all values is too much overhead.

Here is a simple example:

(reduce (fn [acc x]
          (sc/spy  (+ acc (float (/ 1 x)))))
  0 (range -10000 10000))

This throws a DivideByZero exception 10,000 iterations into the reduce, which exceutes in 3.2ms without being wrapped by spy.

However when the sc/spy macro is used, my REPL output is filled with thousands of logging messages like

  At Execution Point 122810 of Code Site -15, saved scope with locals [start__6136__auto__ acc x]
SPY [122810 -15] /Users/yuhan/scratch/repo.clj:74 
(+ acc (float (/ 1 x)))
=>
-6.189866401342442
SPY [122811 -15] /Users/yuhan/scratch/repo.clj:74 
  At Execution Point 122811 of Code Site -15, saved scope with locals [start__6136__auto__ acc x]
SPY [122811 -15] /Users/yuhan/scratch/repo.clj:74 
(+ acc (float (/ 1 x)))
=>
-6.2398664020875
SPY [122812 -15] /Users/yuhan/scratch/repo.clj:74 
  At Execution Point 122812 of Code Site -15, saved scope with locals [start__6136__auto__ acc x]
SPY [122812 -15] /Users/yuhan/scratch/repo.clj:74 
(+ acc (float (/ 1 x)))
=>
-6.292497981427005
SPY [122813 -15] /Users/yuhan/scratch/repo.clj:74 
  At Execution Point 122813 of Code Site -15, saved scope with locals [start__6136__auto__ acc x]
SPY [122813 -15] /Users/yuhan/scratch/repo.clj:74 
(+ acc (float (/ 1 x)))
=>
-6.348053537396481
...

and execution takes ~4900ms, 3 orders of magnitude slower than the original.
(Strangely, spyqt performs even worse, taking ~5400ms to throw the error.)

Of course this becomes unacceptably slow on real examples with many more locals and complicated logic.
It would be nice if there was a way to only store and log locals when an exception is thrown, or turn off logging output entirely (I suspect that's where most of the overhead comes from)

doucmnet possibility to inject into `core.ns` ?

I have lately discovered that it can be very useful to dynamically inject the sc.api macros into clojure.core.

This makes them available in any ns.

Having this in a file "somewhere" and use load-lib or copy paste from somewhere into the repl,
works very well and it might be worth to be documented under tips and tricks.

(ns clojure.core)

(require 'sc.api)
(intern 'clojure.core
        (with-meta 'spy {:macro true})
        @#'sc.api/spy)

(intern 'clojure.core
        (with-meta 'defsc {:macro true})
        @#'sc.api/defsc)

(intern 'clojure.core
        (with-meta 'undefsc {:macro true})
        @#'sc.api/undefsc)

...

This together with a deps.edn alias and some .locals-dir.el magic

makes the sc.api auto-available.

Spy logging interferes with compiled JS in CLJS

I still get logger output interleaved with the JS output that was discussed in #1
when using [vvvvalvalval/scope-capture "0.2.0"] in ClojureScript with Leiningen/cljs-build/figwheel setup.

This problem seems to come and go from project to project or release to release of scope-capture for me. I haven't been able to narrow it down to a basic new project to reproduce it. I think it may be something with the combination of the tools involved.

I'll add that I used to be able to use scope-capture with this same setup around the 2nd release. I am not sure I have enough details to be actionable here. I'm curious if anyone else is facing similar.

Request: alias for "most recent ID"

I just started using this library and it seems to be a really nice lightweight alternative to debuggers :)

However a majority of the time during development, I am incrementally redefining functions and calling them with different arguments, and would only be concerned with inspecting the most recently captured result.

A major source of friction in this REPL driven workflow comes when each function evaluation triggers a new scope capture ID, which means having to manually scroll up the (potentially very large) REPL output to find the line that says eg. SPY [427 -20], take note of the number 427, and go around updating all the IDs in defsc/letsc forms. In a few incidents so far, I entered the wrong ID or forgot to update it, leading to confusion when the captured values seemed to not match up with reality.

(defn foo [x]
  (sc.apy/spy
   (let [var ...]
     ...)
   ))

(foo "abc") ; <- call foo, capturing the scope of var within

(letsc 427 ; <-- this number has to be updated after each function call
  (some-analysis-involving var))

Am I missing some other way of reducing this hassle needed? Ideally I should be able to go back and forth seamlessly between editing a function body, testing it with different arguments and immediately being able to investigate its scope if something behaves unexpectedly.

It would be great if there was an alias to refer to the most recently evaluated ID, much like the *1 variable in the Clojure repl.
The number 0 seems to be a good candidate, since (as far as I can tell) it doesn't have a currently assigned meaning.

(letsc 0 ; <-- refers to the most recent evaluated call to foo
  (some-analysis-involving
   var))

Alternatively one could enter a negative number referring to the capture location (-20 in this example), which would be overloaded to mean "most recent/ maximum capture ID at that particular capture site"

Remove core.async dep, use promise-based impl instead

The original goal of including core.async was to have a portable way of suspending / resuming execution in sc.api/brk. It turns out that core.async is not that portable, for instance self-hosted ClojureScript is not supported; it is also a bit too prescriptive about the green threading mechanism to use for non-blocking breakpoints.

So I'm going to remove the core.async dep, and reimplement sc.api/brk in terms of clojure.core/promise and clojure.core/deliver. There's no breakage here, as core.async has not yet been exposed in the API.

Shortcut to use the `last-ep-id` in `letsc`

Hello !

Because most of the time I'm interested in the last spy, I found that I can inject this macro in my user NS, to avoid having to find the ep-id manually
(and because we can't do (sc.api/letsc (sc.api/last-ep-id) x) )

(defmacro letsc
  [& body]
  (let [ep-id (sc.api/last-ep-id)]
    `(sc.api/letsc ~ep-id ~@body)))

I use it like this

(defn foo [x] (sc.api/spy x))
SPY <-20> /tmp/form-init17184007261798462834.clj:1 
 At Code Site -20, will save scope with locals [x]
=> #'user/foo
(foo 12)
SPY [91 -20] /tmp/form-init17184007261798462834.clj:1 
  At Execution Point 91 of Code Site -20, saved scope with locals [x]
SPY [91 -20] /tmp/form-init17184007261798462834.clj:1 
x
=>
12
=> 12
(letsc x)
=> 12

It's really a nice improvement I think, so I would like to share it with you in case you also think so.

Thanks for scope capture, I love it :)

Document how to register loggers

Hi, long time no see IRL :)

I'm trying to slience the post-eval-logger because the data I want to inspect and play with is big, and logging it hides the Execution point haha.

I've tried registering globally a no-op function with (sc.api.logging/register-cs-logger :sc/spy-ep-post-eval-logger (fn [_])) but it didn't work. The only thing that worked was passing this function explicitly at call site of spy : (sc.api/spy {:sc/spy-ep-post-eval-logger (fn [_])} (my-code ...)).

Bug or mistake ? If it's a mistake, please correct me. In exchange you'll get a PR with documentation for this feature. Deal ?

Customizable, dependency-free, potentially non-blocking breakpoints

In order to make breakpoints available to non-blocking execution environments such as ClojureScript, the macro-expansion of sc.api/brk needs to be customized by some green-threading mechanism.

Currently, the most popular / official green threading mechanism would be core.async's go and <!, i.e the macro-expansion of (sc.api/brk expr) would expand to something like:

(let [ch12 (cljs.core.async/chan)]
  (prompt-breakpoint ch12)
  (let [cmd13 (cljs.core.async/<! ch12)]
    (case (:op cmd13)
      :loose expr 
      :loose-with (:value cmd13)
      :loose-with-err (throw (:error cmd13)))
    ))

Of course, for this to work, the (brk ...) call should be placed inside a go block.

I'm reluctant to use this approach, for several reasons:

  • core.async's not that portable, e.g it's currently not supported on self-hosted CLJS
  • it's prescriptive about the green threading mechanism used; core.async's go is currently the most popular, but it may not always be so in the future, and other such as promesa's or manifold's are desirable as well
  • it imposes to distinguish the emission for Clojure and for ClojureScript, which is always a bit hacky.

Another solution would be to let the client register and use their own strategies for green-threading, by registering a hook in brk. Such strategies could be registered for core.async, manifold, promesa, etc. Examples:

clojure.core promise / deliver / deref:

(defn promise-deliver-port 
  []
  (let [p (promise)]
    {:read-port p
     :write-to-port (fn [cmd] (deliver p cmd))}))

(sc.api.breakpoints/register-green-threading-strategy
  :promise-deliver
  {:make-port `promise-deliver-port
   :emit (fn [cmd-sym read-port body]
           `(let [~cmd-sym (deref ~read-port)]
              ~body))})

core.async go / <!

(defn core-async-port
  []
  (let [c (cljs.core.async/chan)]
    {:read-port c
     :write-to-port (fn [cmd] (a/put! c cmd))}))

(sc.api.breakpoints/register-green-threading-strategy
  :core-async-<!
  {:make-port `core-async-port
   :emit (fn [cmd-sym read-port body]
           `(let [~cmd-sym (cljs.core.async/<! ~read-port)]
              ~body))})

Promesa alet / await

(defn promesa-port
  []
  (let [a (atom nil)
        p (p/promise (fn [resolve reject]
                       (reset! a resolve)))]
    {:read-port p
     :write-to-port @a}))

(sc.api.breakpoints/register-green-threading-strategy
  :promesa-alet-await
  {:make-port `core-async-port
   :emit (fn [cmd-sym read-port body]
           `(p/await (p/alet [~cmd-sym (p/await ~read-port)]
                       ~body)))})

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.