is there a standard practice for adding (dev/start! {:report (pretty/thrower)}) to all REPL sessions of a project? I tried adding the following to the dir-locals for my project
((nil . ((cider-clojure-cli-aliases . "test")
(cider-repl-init-code . ("(when-let [requires (resolve 'clojure.main/repl-requires)]
(clojure.core/apply clojure.core/require @requires))"
"(require '[malli.dev :as dev] '[malli.dev.pretty :as pretty]) (dev/start! {:report (pretty/thrower)}) (println \"malli dev started\")")))))
and despite seeing
;; Startup: /opt/homebrew/bin/clojure -Sdeps \{\:deps\ \{nrepl/nrepl\ \{\:mvn/version\ \"1.5.1\"\}\ cider/cider-nrepl\ \{\:mvn/version\ \"0.58.0\"\}\ refactor-nrepl/refactor-nrepl\ \{\:mvn/version\ \"3.11.0\"\}\}\ \:aliases\ \{\:cider/nrepl\ \{\:main-opts\ \[\"-m\"\ \"nrepl.cmdline\"\ \"--middleware\"\ \"\[refactor-nrepl.middleware/wrap-refactor\,cider.nrepl/cider-middleware\]\"\]\}\}\} -M:test:cider/nrepl
malli: dev-mode started
malli dev started
user>
at the top of my REPL session
instrumentation throws do not take effect, until I additionally add
(dev/start! {:report (pretty/thrower)})
in my repl.
I also checked that, even before calling dev/start! manually (in the situation where no error is thrown)
(malli.core/function-schemas)
correctly contains the schema for the function I want to instrument, so it looks like it's known to the Malli environment.
I think the next step to debug is to check what reporter is currently active, but I'm not sure where to access it -- i dug through the malli docs, and src/malli/instrument.clj, but couldn't quite figure it out. an advice? am i missing something very dumb?that makes sense -- thanks for the detailed thoughts! currently the pattern of using
(defn my-test [a] "hi")
(m/=> my-test [:=> [:cat :int] :string])
is working well enough for me that I'm going to keep using it, but ill look into mx/defn as wellThe specific thing that confused me is, the sequence of calling:
(dev/start! {:report (pretty/thrower)})
;; load the following namespace
(ns mystuff)
(defn foo [x] "invalid")
(m/=> [:=> [:cat :int] :int])
;; then from somewhere else call
(foo "hi!")
results in different behavior from the sequence of calling:
(dev/start! {:report (pretty/thrower)})
;; load the following namespace
(ns mystuff)
;; different ordering of m/=>
(m/=> [:=> [:cat :int] :int])
(defn foo [x] "invalid")
;; then from somewhere else call
(foo "hi!")
maybe it was never the intention for sequence #2 to be well-defined behavior, but it surprised me that they created different results.
(fwiw i wanted to do sequence 2 because I wanted the behavior of malli throws globally for every function that is annotated regardless of when/how it was imported)Re: ordering of defn and =>, the malli.demo example still works for me.
% clj -M:test
Clojure 1.12.3
user=> (require 'malli.demo)
nil
user=> (in-ns 'malli.demo)
#object[clojure.lang.Namespace 0x1dc37d4f "malli.demo"]
malli.demo=> (dev/start!)
malli: instrumented 3 function vars
malli: dev-mode started
nil
malli.demo=> (kikka "1")
-- Invalid Function Input ------------------------------------------------------
Invalid function arguments
["1"]
Function Var
malli.demo/kikka
Input Schema
[:cat :int]
Errors
{:in [0], :message "should be an integer", :path [0], :schema :int, :value "1"}
More information
--------------------------------------------------------------------------------
Execution error (ClassCastException) at malli.demo/kikka (demo.cljc:9).
class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')
malli.demo=> (kukka "1")
-- Invalid Function Input ------------------------------------------------------
Invalid function arguments
["1"]
Function Var
malli.demo/kukka
Input Schema
[:cat :int]
Errors
{:in [0], :message "should be an integer", :path [0], :schema :int, :value "1"}
More information
--------------------------------------------------------------------------------
Execution error (ClassCastException) at malli.demo/kukka (demo.cljc:14).
class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')
malli.demo=> It doesn't work if I do before requiring malli.demo. That's because start! only looks at namespaces that have already been loaded. It doesn't hook new namespace loading.
This is as documented in the start! docstring.
Would some documentation improvements help you here?
yeah I'm not sure how sequence #2 actually does anything, I'll need to recheck the implementation
Riiiight, start! adds a watch on the -function-schemas* atom that does instrument!, and m/=> puts stuff in that atom, so that's why defn + m/=> works (the defn is in place to be instrumented), and m/=> + defn doesn't.
For a usecase like this, maybe mx/defn is more ergonomic? There's definitely a footgun here that could be documented better.
ok, so it turns out that this happened because I reversed the order of the malli schema defs i.e. this does not lint:
(m/=> my-test [:=> [:cat :int] :string])
(defn my-test [a] "hi")
but this does:
(defn my-test [a] "hi")
(m/=> my-test [:=> [:cat :int] :string])
this was really non-obvious to me because the header image uses the old style, and this snippet from the docs implies that both orders should have the same behaviorI would recommend adding this to your dev/user.clj file in a function, and then calling it when you start your repl:
(defn malli-start []
(require '[malli.dev :as dev]
'[malli.dev.pretty :as pretty])
(dev/start! {:report (pretty/thrower)})
(println \"malli dev started\"))
i don't know if that solves the issue for the order of malli schema defs vs actual defs, i didn't read your reply befoer writing that lol
but the way that user.clj is loaded, i think it's probably safest to wait until everything else is loaded before starting instrumentation