Fork me on GitHub
#expound
<
2020-11-18
>
neilyio18:11:17

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.

bbrinck18:11:24

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

neilyio18:11:04

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

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

neilyio18:11:29

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

neilyio18:11:47

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

neilyio18:11:01

As per the Expound FAQ

neilyio18:11:45

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

bbrinck18:11:46

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

bbrinck19:11:23

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.

bbrinck19:11:09

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

bbrinck19:11:57

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

bbrinck19:11:17

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

neilyio19:11:36

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

bbrinck19:11:01

Hm, are you using Chrome?

neilyio19:11:54

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:

neilyio19:11:34

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

neilyio19:11:06

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

bbrinck19:11:16

I see. That looks like the actual error object.

bbrinck19:11:58

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

neilyio19:11:22

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

neilyio19:11:35

Would a normal spec message print in the REPL?

bbrinck19:11:30

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

bbrinck19:11:03

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

bbrinck19:11:00

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

neilyio19:11:33

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

neilyio19:11:40

I'll start without Orchestra and report back.

bbrinck19:11:24

@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

bbrinck19:11:17

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]

bbrinck19:11:11

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

bbrinck19:11:56

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. or whatever port figwheel is running on

neilyio19:11:49

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

neilyio19:11:54

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!

neilyio20:11:06

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

neilyio20:11:44

Instead I'm getting...

neilyio20:11:19

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

bbrinck22:11:51

@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

bbrinck22:11:13

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->str e))
  true)

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

bbrinck22:11:22

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

bbrinck22:11:02

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

neilyio23:11:34

@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.

bbrinck23:11:38

No problem! Hopefully we can get it figured out

neilyio23:11:10

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.

neilyio23:11:15

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:

neilyio23:11:31

(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->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"])

neilyio23:11:04

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

neilyio23:11:45

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:

neilyio23:11:04

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)

neilyio23:11:28

...and Expound works!

neilyio23:11:10

... 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".

neilyio23:11:23

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?

bbrinck23:11:01

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?

bbrinck23:11:10

*clojurescript-version*

bbrinck23:11:08

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

neilyio23:11:43

Got it, *clojurescript-version* reads 1.10.773 .

bbrinck23:11:19

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

neilyio23:11:35

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

neilyio23:11:04

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

bbrinck23:11:19

Ha, I should update that 🙂

neilyio23:11:03

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

bbrinck23:11:54

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

neilyio23:11:15

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

bbrinck23:11:12

No worries.

neilyio23:11:50

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

bbrinck23:11:53

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!)