This malli PR improves the caching of validators, explainers, and parsers in some subtle (but I think common) cases. If anyone can try this on a large system I'd be curious if it has impact on startup time and memory use, or any other feedback. https://github.com/metosin/malli/pull/1180
It appears some optional schemas can't be used in local registries, for reasons I don't quite understand. (Something to do with them being opaque objects and not pure data?)
(m/validate [:schema {:registry (mu/schemas)}
[:union
:string :int]]
"foo")
;=> #error {:data {:type :malli.core/child-error, :message :malli.core/child-error, :data {:type :select-keys, :properties nil, :children nil, :min 2, :max 2}}
I've been resisting the temptation to change the default registry, but I'm trying to use some of these optional schemas with m/=>, which doesn't take an options argument. Is there any workaround here, or do I have to bite the bullet and make a global mutation to the default registry?You can pass the constructors directly to malli instead of referring to them by keyword.
m/schema [(m/-any-schema)])
:anyHmm, good idea. I'll have to mull over how much I value the clean look of using the keywords, but I think I'll probably go with that.
It's certainly in the category of last-ditch workaround. You might just be kicking the can further down the road to a different problem.
related, I'm working on an extensible default registry here to try and establish something more idiomatic for this common issue https://github.com/frenchy64/malli/pull/28/files
(m/reg-ctor ::any-alias (m/-any-schema))
(m/doc ::any-alias)
;TODO
; Schema constructor
; {:file "/Users/ambrose/Projects/malli-local-dev/register-macro/test/malli/doc_test.cljc", :ns malli.doc-test, :line 15, :column 1, :form (m/reg-ctor :malli.doc-test/any-alias (m/-any-schema)), :schema-form (m/-any-schema), :into-schema true}Ah, I like that! No reason not to just borrow spec's mutable-but-in-a-principled-way approach.
thanks! here's my new s/def for regular schemas 🙂
(m/reg ::another-schema
"A documentation string"
{:a static-meta-map}
:boolean)
(m/doc ::another-schema)
; -------------------------
; Named Schema
;
; A documentation string
;
; :bool
;
; Source code:
; (m/reg ::another-schema
; "A documentation string"
; {:a static-meta-map}
; :bool)
Haha, also learning from the "would really love to have metadata on these" complaint about spec, I see.
yes, and answering the question of "where do we put this stuff" as "only during macro-time" with a malli spin of "you can then export it all to a file if you need it later"
The use of namespaced keys in a global registry is one of my favorite things about spec. Would love to see this land.
yes the only downside to me is that they print terribly.
And the more robust your namespacing, the uglier they are
yes. for example, Typed Clojure types print differently based on the current namespace. partially because it uses symbols for names, but also because the parser is namespace-sensitive.
I don't think this is something that would work for malli, the latter point being the real problem. spec has the short form printing, but you can't do anything with the result except look at it.
It kinda feels like printing and serializing ought to be distinguished. But that would interfere with a lot of stuff in the REPL.
hmm actually here's an idea. we attach the namespace that the schema is relative to in the output of m/form
(m/form [::foo]) => ^{:relative-to *ns*} [:foo}
then you can roundtrip
Could work. Though you'd need to opt into printing metadata.
Why not in the ordinary properties map?
The meta is just for the impl, let me walk through a use case
I don't think we can use m/form, maybe a new op m/pretty-form
but the idea is we override the print-method of the schemas to m/pretty-form relative to the current ns
(prn (m/schema ::foo))
[:_/foo]
then you can always capture the output if you really want to roundtrip
but most of the time this is just for humans
oh, well you can't capture the stdout. but what I mean is that you should be able to roundtrip with (-> ::foo m/schema m/pretty-form m/schema)
Yeah, I got what you mean
so things like explain errors are pretty
You can round trip in memory, but not through serialization
yeah
if you keep the same ns, you can roundtrip
Potentially clj serialization with print-meta, but never through edn
I think that's a normal clojure assumption
then you can go a step further and use ns-aliases to pretty print everything else.
which you then consult to parse it again
If the ns aliases were captured explicitly in the top-level properties map, you could have a compromise which can also round trip through edn. Ugly, but all the ugly is in one place and kept away from what matters.
This may also be useful for authoring directly in EDN.
yes that very much has an xml or json schema flavor
True. A mark in its favor, imo! XML is annoying but its namespacing is excellent.
as in, we're following tech that take namespacing very seriously
I like all these ideas.
It would also mean Malli solves declarative aliasing before EDN itself does 😂
omg
m/pretty-form and m/compact-form?
related, it's always bothered me that malli's core schemas aren't namespaced.
I generally feel that the authors of any given library are entitled to use un-namespaced keywords, and it's incumbent on the lib users to avoid defining their own. A little like how Clojure (and EDN) reserves un-namespaced reader tags for its own future extensions.
In fact I think all of Malli's examples of schema-local registries use namespaced keywords.
but would xml have stood for that?
clojure.core is not in the global namespace
I get what you're saying though, just being fussy
XML didn't define a core vocabulary. It's a little different.
But I do in general feel like namespaced keywords are underused. They're a killer feature and make a lot of otherwise unthinkable operations trivially safe, if you're disciplined about using them.
yes. it's the "discipline required" part that bothers me. spec forces specs to be namespaced because they're macros and hence tied to a namespace.
I always felt like it was a little bit of a nudge from Rich to encourage people to use nsed keywords more, too.
He talks about maps flowing through the system, containing keys from all over the place. That's only possible if you're heavily using namespaces.
I wonder sometimes if a small ergonomic tweak might have led to different habits: swap the meaning of : and ::
yes. what I love about vars is that it's impossible to not namespace them. you don't need to know about the benefits of namespacing to use them properly. they come with huge baggage of namespace loading which nsed keywords fixes, but the extra discipline needed is a conundrum.
This was in fact the feature that got me to dig deep into Clojure in the first place. "Just namespace every identifier" was huge after working in OO systems where method name collision could be a big headache.
And I was downright confused when I explored the ecosystem and saw such little use of that superpower.
vars?
Namespacing in general, symbols and keywords both. The way protocols improve on Java interfaces by adding namespacing was a big revelation for me, but the way this strength generalized to data in maps was pretty immediately clear to me.
I didn't decomplect vars into identifier+value until way after spec existed so it wasn't obvious to me. but it's clearly sunk it.
which I guess makes my point about discipline very personal 🙂
Vars proper took me a while to grok. But the basic idea that the "method" had a qualified name is what jumped at out me.
ah yes. I took a long time (rightly so in hindsight) to fully understand the implications of the interface method being unqualified.
I had the benefit(?) of just recently, at the time I picked up Brave and True, finding an idea of mine impossible because of a method name collision.
in fact it was a method name collision – the method was called name
yes it makes protocols need to be slightly leaky abstractions to fully utilize them
to know you needed to use extend
This was a problem I encountered in a pure Java system (where I wasn't going to get away with introducing Clojure), to be clear – so I was seeing how much better things could be, if only I were using Clojure
Anyway, it's been genuinely delightful chatting, but I meant to log off 40 minutes ago and really need to get going now 😂 Really looking forward to your PR landing!
see ya!
First step towards reversibly compacting qualified schema forms https://github.com/frenchy64/malli/pull/30
(is (= [:tuple
{:aliases {:malli.core-test :_,
:malli.generator :mg}
:registry {:_/a [:ref :_/b],
:_/b :mg/gen,
:mg/gen :_/a}}
:_/a :_/b]
(m/compact-form [:tuple
{:registry {::a [:ref ::b]
::b ::mg/gen
::mg/gen ::a}}
::a ::b])))After exploring this a bit, it's much better to just compact the edn itself. I didn't find any benefits to supporting the syntax directly in the schemas.