This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-11-08
Channels
- # 100-days-of-code (1)
- # admin-announcements (1)
- # aleph (1)
- # announcements (9)
- # beginners (125)
- # cider (1)
- # cljs-dev (80)
- # cljsrn (2)
- # clojure (82)
- # clojure-czech (1)
- # clojure-dev (5)
- # clojure-finland (1)
- # clojure-italy (16)
- # clojure-nl (6)
- # clojure-spec (24)
- # clojure-uk (39)
- # clojurescript (35)
- # community-development (49)
- # core-async (3)
- # cursive (31)
- # data-science (17)
- # datomic (21)
- # emacs (5)
- # fulcro (92)
- # graphql (1)
- # jobs (2)
- # lambdaisland (1)
- # leiningen (19)
- # luminus (9)
- # off-topic (21)
- # parinfer (6)
- # pedestal (1)
- # portkey (2)
- # re-frame (12)
- # reagent (8)
- # reitit (4)
- # shadow-cljs (117)
- # spacemacs (5)
- # specter (4)
- # sql (2)
- # testing (2)
- # tools-deps (3)
- # vim (1)
I think this is a bug, but I’d like to verify. This works on clj but not on cljs. The output of instrumentable-syms
should be valid as input for instrument
.
(require '[clojure.spec.alpha :as s])
(require '[clojure.spec.test.alpha :as stest])
(defn foo [x])
(s/fdef foo :args (s/cat :x int?))
(defn bar [x])
(s/fdef bar :args (s/cat :x int?))
(def syms (stest/instrumentable-syms))
(println syms)
(stest/instrument (disj syms `bar))
(try (foo "string")
(catch #?(:clj Exception :cljs :default) _
(println "foo throws")))
(bar "string") ;; bar doesn't throw
this gets tedious fast if you can only pass a literal expression. e.g. in one function I had to spell out all possible instrumentable syms but one.
Ahh, when I try this in a REPL, it has heartburn trying to eval
the form (disj ...)
, being unable to resolve the symbol.
@borkdude instrument is macro, if you need to do fancier stuff you also need to write a macro
or write a spec for it, but since it’s part of spec, I’m not sure that works - maybe it would
Yeah, I was mistaken about the use of eval
in the macro. (It is really there just to help massage compile time data that might be using quoting.)
I’m not sure what the macro should look like when you want to instrument all but one sym:
(defmacro my-macro [exclude-sym]
(let [isms (stest/instrumentable-syms)
wo-foo (disj isms exclude-sym)]
`(stest/instrument ~wo-foo)))
Here I get: Caused by: java.lang.AssertionError: Assert failed: (symbol? sym)
Splicing in with ~@wo-foo
gets me: Caused by: java.lang.IllegalArgumentException: Don't know how to create ISeq from: repro$bar
where the call is
(my-macro `foo)
Got it. By adding this clause to form->sym-or-syms:
(cond
(::no-eval (meta sym-or-syms))
(second sym-or-syms)
(every? symbol? sym-or-syms)
sym-or-syms
:else
(eval sym-or-syms))
something like
(stest/instrument [cljs.user/foo cljs.user/bar])
becomes possible. The rest of the cljs tests all pass. And this fixed my macro problem.If we allow both
(stest/instrument [`foo `bar])
and (stest/instrument [cljs.user/foo cljs.user/bar])
then this kind of macro becomes possible:
(defmacro instrument-without [exclude-syms]
(let [isms (stest/instrumentable-syms)
wo-sym (apply disj isms exclude-syms)]
`(stest/instrument ~wo-sym)))
I’m not sure it’s possible without that change.Got it:
(defmacro instrument-without [exclude-syms]
(let [exclude-syms (map second exclude-syms)
isms (stest/instrumentable-syms)
filtered (apply disj isms exclude-syms)
filtered (mapv (fn [sym]
[sym]
(list 'quote sym))
filtered)]
`(stest/instrument ~filtered)))
(instrument-without [`foo])
@borkdude instrumentable-sysms
is a runtime function, right? How does your macro call it?
Ahh, I see, the macro above can work in self-hosted. To make one that works in JVM as well, I would just riff on the shipping instrument
:
(defmacro instrument-without [exclude-syms]
`(stest/instrument '[~@(apply disj
(#?(:clj s/speced-vars
:cljs cljs.spec.alpha$macros/speced-vars))
(eval exclude-syms))]))
This code worked for me on the JVM:
(ns repro.core
(:require [clojure.spec.alpha :as s]
[clojure.spec.test.alpha :as stest])
#?(:cljs (:require-macros [repro.core :refer [instrument-without]])))
(defn foo [i])
(s/fdef foo :args (s/cat :i number?))
(defn bar [i])
(s/fdef bar :args (s/cat :i number?))
(defn baz [i])
(s/fdef baz :args (s/cat :i number?))
(defmacro instrument-without [exclude-syms]
(let [exclude-syms (map second exclude-syms)
isms (stest/instrumentable-syms)
filtered (apply disj isms exclude-syms)
filtered (mapv (fn [sym]
[sym]
(list 'quote sym))
filtered)]
`(stest/instrument ~filtered)))
(defn -main [& args]
(println "hello" (instrument-without [`foo])))
#?(:cljs (set! *main-cli-fn* -main))
OK, trying to understand what you did with a macro sitting in the same namespace with -main
🙂
compiled code does the same as called with -m:
clj -R:cljs/dev -m cljs.main -re node -c repro.core
I only got it to work in a multi-segmented namespace, I think because there is some code looking for "."
in the symbols
I'm referring to foo
, etc.
So, if you instead
#?(:cljs (defn foo [i]))
and put all of the other stuff like that in conditional blocks so that they are not defined in Clojure, then you will see a difference in output.In other words, I suspect you are getting it to print things that look correct only because it is actually Clojure's spec implementation doing it, not ClojureScript's.
The above macro wouldn't work in Clojure, so a truly 3-way portable one would look uglier.
The macro is currently assuming that the runtime spec and spec.test namespaces are already loaded. Here is a reference to the full thing showing it working: https://gist.github.com/mfikes/c1b6ce9b7a3a80559774bd89accaea50
The main bit you may be missing is the need to require cljs.spec.alpha
, not clojure.spec.alpha
in the macro namespace. (Otherwise you are getting Clojure's implementation, which doesn't have s/speced-vars
)
that’s indeed tricky when you refer to clojure.spec.alpha
in a .cljc
file, I didn’t realize that. Is that also something that could go wrong in speculative.core.cljc right now?
Not a problem in Speculative. The issue is not really whether clojure.*
is being required from a .cljc
file, but whether the file is being compiled as Clojure or ClojureScript. When it is Clojure, you get the actual namespace being required, and when it is ClojureScript, clojure.*
-> cljs.*
aliasing kicks in.
I still don’t fully get it. When I run this:
clj -R:cljs/dev -m cljs.main -re node -m repro.core
I’m compiling the file as ClojureScript, no?But, it then requires macros on itself, which in JVM ClojureScript causes it to be compiled as Clojure as well, for the macros namespace compilation.
and when you’re making a macro in a cljc file and you self-refer it, it also works like that
A simple way to understand this macro: It is the 0-arity version of stest/instrument
, with a slight modification to (apply disj ... (eval excluded-syms))
Right. That’s, strictly speaking, an internal optimization that comes into play when dealing with a crap ton of specs
I’m wondering if I’m running into a similar issue here: https://github.com/borkdude/speculative/blob/instrument-all/test/speculative/core_test.cljc#L16 The specs aren’t instrumented, 0 of them, in JVM cljs.
Truth be told, you probably want ::no-eval
, but it’s unfortunately not part of the public contract ¯\(ツ)/¯
I think I might be running into a problem with the 0-arity in the link above. At compile time nothing is instrumented yet, so the test doesn’t pass. Does that make sense?
although it first requires speculative.core so that doesn’t make sense to me either.
This is weird. Commenting out the require on clojure.spec.alpha makes the instrument-all test pass… https://github.com/borkdude/speculative/commit/559caea91f7beb5cbf6b98b2a068d96f472185aa#diff-942e8a56b45a1acf66fc6a611eb6aa1dR3