expound

neilyio 2020-11-18T18:56:17.016600Z

I'm having a really hard time getting Expound set up with ClojureScript in the browser. I've been at it for a couple hours and I haven't even seen an Expound error message yet.

bbrinck 2020-11-18T18:57:24.018200Z

Can you talk a little bit more about your use case? Are you instrumenting functions?

neilyio 2020-11-18T18:58:04.019Z

That's what I started trying to do, instrumentation with Orchestra, setting up with something like:

(st/instrument)
(set! spec/*explain-out* expound/printer)

neilyio 2020-11-18T18:58:29.019900Z

But reading through the docs more has suggested that instrumentation isn't completely ready to go in Expound... is that still the case?

neilyio 2020-11-18T18:58:47.020500Z

(I realize it's not necessarily Expound, I meant to say the ClojureScript REPL)

neilyio 2020-11-18T18:59:01.021100Z

As per the Expound FAQ

neilyio 2020-11-18T18:59:45.022200Z

If instrumentation isn't the solution for the browser, what's the suggested workflow?

bbrinck 2020-11-18T18:59:46.022300Z

So Expound isn’t actually involved in instrumentation at all.

bbrinck 2020-11-18T19:00:23.023500Z

What happens is that you instrument with spec or orchestra, but then you need to make sure Expound is used to actually print the instrumentation errors.

bbrinck 2020-11-18T19:01:09.024800Z

Unfortunately, it can be unclear how to do that and it depends on the use case

bbrinck 2020-11-18T19:01:57.026200Z

If you’re in a REPL, I would think the above would work. But in the browser, you may not be in the context of a REPL - it might just be that the errors are being thrown from your running program

bbrinck 2020-11-18T19:02:53.026900Z

This section of the README was be of help https://github.com/bhb/expound#clojurescript-considerations

bbrinck 2020-11-18T19:03:17.027300Z

Namely > To format errors in the browser, you must set up some global handler to catch errors and call repl/error->str.

neilyio 2020-11-18T19:03:36.027900Z

Thanks for that! I did try that section of the README with no luck.

bbrinck 2020-11-18T19:04:01.028900Z

Hm, are you using Chrome?

neilyio 2020-11-18T19:04:07.029Z

I am.

neilyio 2020-11-18T19:04:54.030100Z

I don't even think I have Expound messages appearing in the REPL. When I evaluate an expression (which I know has a spec error) in an Emacs buffer, I just get this in the minibuffer:

neilyio 2020-11-18T19:04:59.030200Z

neilyio 2020-11-18T19:05:34.031400Z

A little hard to read, especially because cider-inspect doesn't work with ClojureScript..

neilyio 2020-11-18T19:06:06.032400Z

Nothing is printed in the REPL, to be clear. That's just the returned value.

bbrinck 2020-11-18T19:06:16.032600Z

I see. That looks like the actual error object.

bbrinck 2020-11-18T19:06:58.033600Z

So even without Expound, it looks like something is amiss because you might expect to see a normal spec message

neilyio 2020-11-18T19:07:22.034Z

That could be a clue, I'm also new to spec so I have no idea what I'm even looking for.

neilyio 2020-11-18T19:07:35.034300Z

Would a normal spec message print in the REPL?

bbrinck 2020-11-18T19:08:30.034700Z

Yeah, normally a spec error would print like this https://clojure.org/guides/spec#_instrumentation

bbrinck 2020-11-18T19:09:03.035500Z

I might suggest starting without Orchestra or Expound and just get normal spec messages working first.

bbrinck 2020-11-18T19:10:00.037Z

I don’t know the details of your CLJS REPL, but what I would expect to happen is 1. You instrument the function 2. You call the function 3. An error is thrown 4. The REPL catches the error, and then prints a string error message to the console

neilyio 2020-11-18T19:10:33.037400Z

Thanks for the tip! I'll do exactly that, I guess the problem could be anywhere.

neilyio 2020-11-18T19:10:40.037800Z

I'll start without Orchestra and report back.

bbrinck 2020-11-18T19:17:24.038400Z

@neil.hansen.31 To get you started, here’s a simple example of how it might work in a bare-bones CLJS REPL clj -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "1.10.764"}}}' -m cljs.main

bbrinck 2020-11-18T19:18:17.038800Z

ClojureScript 1.10.764
cljs.user=> (require '[clojure.spec.alpha :as s])
nil
cljs.user=> (require '[clojure.spec.test.alpha :as st])
nil
cljs.user=> (defn foo [x] x)
#'cljs.user/foo
cljs.user=> (s/fdef foo :args (s/cat :x int?))
cljs.user/foo
cljs.user=> (st/instrument foo)
Unexpected error macroexpanding cljs.spec.test.alpha/instrument at (<cljs repl>:1:1).
Unable to resolve symbol: foo in this context
cljs.user=> (st/instrument `foo)
[cljs.user/foo]
cljs.user=> (foo 1)
1
cljs.user=> (foo "")
Execution error - invalid arguments to cljs.user/foo at (<cljs repl>:1).
"" - failed: int? at: [:x]

bbrinck 2020-11-18T19:21:11.041100Z

You can compare that to what you’re seeing - I don’t know the details of the cider CLJS repl, but it could be configured in a way that it is not print spec errors as expected

bbrinck 2020-11-18T19:23:56.043600Z

This isn’t specific to Expound, but FWIW, if the cider CLJS repl is giving you issues, you could try the following if you happen to use Figwheel (I don’t know anything about shadow-cljs) 1. Start normal nrepl REPL 2. M-x cider-connect 3. `(use ’figwheel-sidecar.repl-api) 4. (start-figwheel!) 5. (cljs-repl) Then open the browser e.g. <http://localhost:3446/index.html> or whatever port figwheel is running on

neilyio 2020-11-18T19:34:49.044400Z

Great example, thank you @bbrinck. It's helpful to know what I should be expecting.

neilyio 2020-11-18T19:35:54.045600Z

My whole project is shadow-cljs at this point, so I'll have to google around by this. It's all pretty overwhelming... I really hope I finally get to use your tool at some point, I've heard so much about it!

neilyio 2020-11-18T20:06:06.046400Z

Sigh... this is exhausting... I really want messages in my browser like the ones provided by Ghostwheel...

neilyio 2020-11-18T20:06:10.046500Z

neilyio 2020-11-18T20:06:44.047Z

Instead I'm getting...

neilyio 2020-11-18T20:06:49.047100Z

neilyio 2020-11-18T20:07:19.047900Z

... which is only slightly better than a spec-less JS error message.

bbrinck 2020-11-18T22:09:51.049700Z

@neil.hansen.31 I’m certainly not an expert in Shadow-CLJS, so take this with a grain of salt, but it looks like we may be discussing two different issues at once 1. In a CLJS REPL, you’re not seeing spec errors printed 2. In a browser session, errors are being raised and not handled

bbrinck 2020-11-18T22:11:13.050300Z

For the latter, have you tried setting a global JS error handler e.g.

(ns app.core 
  (:require [cljs.spec.alpha :as s]
            [expound.alpha :as expound]
            [cljs.repl :as repl]))

(set! s/*explain-out* expound/printer)

(defn error-handler [message url line column e]
  (js/console.error e)
  (print (repl/error-&gt;str e))
  true)

(set! (.-onerror js/window) error-handler)
source: https://github.com/bhb/expound/issues/152

bbrinck 2020-11-18T22:12:22.051200Z

Also, I haven’t looked at the code, but either Ghostwheel or Guardrails may have some code that explains how they work https://github.com/fulcrologic/guardrails

bbrinck 2020-11-18T22:14:02.051700Z

(namely, how they presumably catch JS errors and then print them w/ expound)

neilyio 2020-11-18T23:34:34.052400Z

@bbrinck thanks for hanging in with me, after doing some investigation all day I've uncovered some new mysteries, as well as a few of my own stupid mistakes.

bbrinck 2020-11-18T23:35:38.053900Z

No problem! Hopefully we can get it figured out

neilyio 2020-11-18T23:36:10.054500Z

1st dumb mistake... I was calling (set! s/*explain-out* expound/printer) after the :require calls to my other .cljs files... so obviously my naive spec tests were running before I had even configured Expound properly.

neilyio 2020-11-18T23:37:15.055900Z

So, with a minimal .cljs file, I have now found ways to get three different outputs in the Chrome DevTools. I'll show you! First off, here's the minimal .cljs file:

neilyio 2020-11-18T23:37:31.056100Z

(ns cards.preload
  (:require
   [cljs.repl :as repl]
   [expound.alpha :as expound]
   [cljs.spec.test.alpha :as stest]
   [cljs.spec.alpha :as spec]))

(set! spec/*explain-out* expound/printer)

(defn error-handler [message url line column e]
  (js/console.error e)
  (print (repl/error-&gt;str e))
  true)

(set! (.-onerror js/window) error-handler)

(spec/fdef result
  :args (spec/cat :m map?))

(defn result [m]
  (merge m {:foo "bar"}))

(stest/instrument `result)

(result [:bar "baz"])

neilyio 2020-11-18T23:38:04.056800Z

That last line is the incorrect call to the function result. Here's what this outputs in the browser:

neilyio 2020-11-18T23:38:28.056900Z

neilyio 2020-11-18T23:39:45.058100Z

Now, after this has loaded, if I trigger a Hot Reload (by adding a newline, w.e.)... I get an additional error in the DevTools that looks different. This time, there's data attached to it:

neilyio 2020-11-18T23:39:52.058200Z

neilyio 2020-11-18T23:41:04.059300Z

Finally, I get a different output if I replace that the last line (the incorrect result call) with this:

(.setTimeout js/window #(result [:bar "baz"]) 3)

neilyio 2020-11-18T23:41:28.059600Z

...and Expound works!

neilyio 2020-11-18T23:41:34.059700Z

neilyio 2020-11-18T23:43:10.061800Z

... I realize when I say "Expound works!", I'm probably referring to any number of things that may have been set into place by this little ceremony... I'm sure Expound always works if all the other pieces are set up right, I mean to say "I'm finally getting beautiful error messages in DevTools".

neilyio 2020-11-18T23:44:23.063100Z

Why this is happening is a total mystery to me. I imagine that in those 3 seconds that I'm waiting to call the result function, something else is getting set up behind the scenes that is necessary for me to the correct error formatting. Do you have any ideas?

bbrinck 2020-11-18T23:49:01.064300Z

So one thing that jumps out (maybe a red herring, but just trying to make sure we’re looking at the same thing) is that cljs.spec indicates a fairly old version of CLJS. What version are you on?

bbrinck 2020-11-18T23:49:10.064700Z

*clojurescript-version*

bbrinck 2020-11-18T23:50:08.065700Z

The reason I ask is because there were some changes to how errors were handled at some point, so what works on my version may not work on yours

neilyio 2020-11-18T23:50:43.066400Z

Got it, *clojurescript-version* reads 1.10.773 .

bbrinck 2020-11-18T23:51:19.067900Z

Hm, I must be wrong. I guess both namespaces are supported :man-shrugging: !

neilyio 2020-11-18T23:51:35.068300Z

I'm just using it because I don't know any better. What's the story there? Documentation everywhere seems to use it interchangeably with clojure.spec.alpha

neilyio 2020-11-18T23:52:04.068600Z

It looks like it's used in the Expound README as well.

bbrinck 2020-11-18T23:52:19.068900Z

Ha, I should update that 🙂

neilyio 2020-11-18T23:53:03.069400Z

Is cljs.spec.alpha outdated now? Should I use clojure.spec.alpha instead?

bbrinck 2020-11-18T23:53:54.070Z

I think that’s more idiomatic, but I actually don’t think it matters in this case (my mistake)

neilyio 2020-11-18T23:54:15.070500Z

No problem, I'm just getting started on spec so apologies that I'm not up to speed.

bbrinck 2020-11-18T23:56:12.070700Z

No worries.

neilyio 2020-11-18T23:56:50.071500Z

What do you think is going on with this setTimeout thing? Is there something I need to wait for as the browser loads?

bbrinck 2020-11-18T23:59:53.074300Z

I took a look and I don’t have a good idea. My guess is that the 3 seconds is not what matters - I would guess that you could shrink that window arbitrarily and get the same result (but I’d be curious to know!)