February 24, 2017

Introducing vim-sayid

If you saw the video for Sayid, I bet the first thing you thought was, "Wow, too bad that only works for Emacs. Someone should write a Vim plugin for that." So did I! And so I did!

vim-sayid

What is Sayid?

Sayid is a Clojure debugger created by Bill Piel. It allows you to inspect the input and output of functions that you mark to be traced.

A brief aside

It was two long weekends learning how Sayid, nREPL middleware, Emacs LISP and Vim plugins work. And then of course making my own plugin work. Even with limited documentation, the nREPL-specific Sayid code was pretty straightforward to read.

One neat trick Bill employed to create a Var to find all other Vars in the namespace tagged with :nrepl.

(def sayid-nrepl-ops
  (->> *ns*
       ns-interns
       vals
       (filter #(-> % meta :nrepl))
       (map #(vector (-> % meta :name str) %))
       (into {})))

Later on he uses this to automatically build the nREPL-middleware functions.

(set-descriptor!
  #'wrap-sayid
  {:handles (zipmap (keys sayid-nrepl-ops)
                    (repeat {:doc "docs?"
                             :returns {}
                             :requires {}}))})

I think that's pretty cool!

What is vim-sayid?

vim-sayid is a Vim plugin that allows Clojure developers who use vim-fireplace to debug code with Sayid.

How do we use Sayid?

Imagine we have two functions, mul and compute.

(defn mul [a b]
  (- a b))

(defn math [a b c]
  (+ a (mul b c)))

When we evaluate (math 1 2 3) we'd expect 7, but in fact math returns 0. That's definitely strange.

While we could follow the flow of the code to find the bug, let's see how Sayid could follow the flow for us!

Debugging with vim-sayid

First, let's trace the namespace with gst. Now that we've established tracing, anytime a function in this namespace is evaluated Sayid will have a record of its inputs and outputs.

With that, let's evaluate (math 1 2 3) again. We get the same result of 0, but let's get a deeper look at why with Sayid.

gsw will present us the Sayid workspace, which contain a log of all the function traces.

v sayid-test.core/math  :22109
|v sayid-test.core/mul  :22110
|^ 
| sayid-test.core/math  :22109
^ 

Well, well. What do we have here? This shows us that we called math which, in turn, called mul. That's great to see, but we can do better.

With our cursor over the sayid-test.core/math line, let's enter the normal command ii. This will present us the input and output of specifically Sayid trace :22109.

v sayid-test.core/math  :22109
| a => 1 
| b => 2 
| c => 3 
| returned =>  0 
^ 

Now we're getting somewhere. Recall that we evaluated (math 1 2 3) and we got back 0. Sayid has the log of that. That's great to see, but we can do even better.

Let's hit backspace to return to the workspace. With our cursor back over the sayid-test.core/math line, let's try out id (think "inspect descendents").

v sayid-test.core/math  :22109
| a => 1 
| b => 2 
| c => 3 
| returns =>  0 
|v sayid-test.core/mul  :22110
|| a => 2 
|| b => 3 
|| returned =>  -1 
|^ 
| sayid-test.core/math  :22109
| a => 1 
| b => 2 
| c => 3 
| returned =>  0 
^ 

Pay dirt.

id gives us the inputs and outputs of trace :22109 and all its descendents. Take a look at the input and output of mul. Hey! (mul 2 3) came back as -1. I bet there's a bug in mul.

(defn mul [a b]
  (- a b))

Wait a second...we should be multiplying, not subtracting. Let's fix that.

(defn mul [a b]
  (* a b))

Excellent, now we'll reload the namespace. We need to trace the namespace with gst again. Let's evaluate (math 1 2 3) one more time!

Sweet! It's 7. Exactly what we wanted. I leave it to you to figure out how to inspect the results in the Sayid workspace.

This is just the tip of the iceberg

This was a pretty simple example, and certainly we could have figured it out with a simple glance at our code. But imagine instead of one namespace, it's five. And instead of two functions, it's twenty. Sayid can handle all that and more.

So, take a look at vim-sayid. If you're doing Clojure development on Vim, it's a pretty nifty tool that can help you out of a bind.

Thanks!

Thanks to Bill Piel for writing Sayid, to Tim Pope for writing vim-fireplace and to Steve Losh for writing an awesome guide on writing Vim plugins!

Tags: vim-sayid clojure vim vim-plugins sayid