More debugging tricks - saving all input/output per function call

I’ve updated my library for storing local variables.

Here’s a small example of the new features:

(use 'miracle.save)

(defn yo
  ([x] x)
  ([x {:keys [a b]}] (+ x a b))
  ([x y & rest] (apply + x y rest)))

(save-var* 'yo)

(yo 5)
(yo 5 {:a 20 :b 30})
(yo 5 30 40)
(yo 5 30 40 50)
@f-saves
;; =>
;; {#'user/yo
;;  ({:args {x 5, y 30, rest (40 50)},
;;    :spec-args ([x 5] [y 30] [rest (40 50)]),
;;    :ret 125}
;;   {:args {x 5, y 30, rest (40)},
;;    :spec-args ([x 5] [y 30] [rest (40)]),
;;    :ret 75}
;;   {:args {x 5, a 20, b 30},
;;    :spec-args ([x 5] [:arg-1 {:a 20, :b 30}]),
;;    :ret 55}
;;   {:args {x 5}, :spec-args ([x 5]), :ret 5})}

Basically you define a function, and then you call save-var* on it, then you’ll save all input and output to that function. :slight_smile: There is still work to be done, but I wanted to share this update.

You also get save-ns* which applies save-var* to every function var in the specified namespace. :slight_smile:

6 Likes

Are you aware of https://github.com/vvvvalvalval/scope-capture? I think there’s some overlap with that.

I’ve looked at it, and you’re right! But I haven’t found any way to instrument many functions easily. Though it may well be me who’s blind.

It just reminded me of it, although I’m not an expert in either library :-).

Ah I see. :slight_smile: There is definitely overlap, miracle.save has grown organically to match my needs, and since I mostly work in clr I’m not very good at looking at other libraries, since they’re generally jvm dependent. But scope-capture does seem to have little if any interop! :slight_smile:

I tend to use a simplified version quite often:

(def dbg-state (atom []))
(defn dbg [x]
  (swap! dbg-state conj x)
  x)

(defn my-currently-not-working-function [x]
  ;; ...
  (dbg x)
  ;; ...
)

I do like the way you use vars to keep track of which where things was debugged from. Scales nicely. A use case where this often makes sense to me is deep within some server handles, when I want to minimize what I’m not understanding.

It’s interesting that you chose to do this function-based. Essentially capturing function input and output. That’s different from how scope capture works. I also like that you get to see a nice progression over time.

@saikyun – What do you think about auto-generating an example based (deftest ...) based on the usage you’ve captured?

1 Like

Ah yeah, that’s basically what I begun with. :slight_smile: One point of my library is to inspire that kind of debugging. Without the comments it’s 186 loc, so I feel it should be pretty easy to grasp.

Yeah, I use it when developing my games, since a function might work 190/200 invocations, and it’s pretty hard to use prn-debugging or breakpoints in that situation. :slight_smile:

Yeah, I personally like the workflow of defing local variables in order to eval-last-sexp inside functions, and as long as your functions are pure, in/out should be enough to figure out any faulting function. :slight_smile:

Yeah, there’s some cool stuff you can do with that. I’ve messed around a bit with creating a GUI in order to filter among saved function invocations; think excel sheet with each invocation being a row, and each parameter being a column. Then you can filter/map over each row in order to figure out a lot of data at the same time. Hard to express in words. ^^;;;

I’ll one up ya; how about generating specs and then create generative tests based on those specs? :sunglasses:

Jokes aside, I think overall there’s a lot of stuff you can do with this. :slight_smile: It’s very easy to apply to someone else’s library as well, so I think it can be a tool to learn about how other people’s code acts as well. It would be easy to add another field that keeps track of the call stack for functions, so you could visualize the routes the data takes á la proto-repl. https://youtu.be/buPPGxOnBnk?t=1692

4 Likes