This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-05-03
Channels
- # aws (6)
- # babashka (77)
- # beginners (102)
- # clj-kondo (24)
- # cljsrn (17)
- # clojure (40)
- # clojure-australia (15)
- # clojure-europe (50)
- # clojure-nl (4)
- # clojure-uk (4)
- # clojurescript (9)
- # conjure (2)
- # cursive (28)
- # data-science (1)
- # datomic (21)
- # events (5)
- # joker (15)
- # malli (136)
- # meander (1)
- # off-topic (25)
- # pathom (4)
- # podcasts-discuss (2)
- # portal (12)
- # portkey (1)
- # powderkeg (1)
- # practicalli (5)
- # re-frame (14)
- # reitit (3)
- # shadow-cljs (49)
- # specter (4)
- # tools-deps (4)
- # unrepl (1)
@joel380 not atm. The new malli schemas of malli schemas
feature allows us to describe the available properties formally as schemas, but it’s just the mechanism atm, no content shipped. Once all schmas are described (both properties and children), it will be THE documentation. For now, you need to read the sources. Documentation PRs always welcome.
e.g.
;; currently
(m/properties-schema :string)
; => nil
;; after
(m/properties-schema :string)
; [:map
; [:min {:description "length must be at least"} :int]
; [:max {:description "length must be at most"} :int]]
… different schema applications (e.g. generation) will have their own overlays, which can be merged to get the full set of available keys. Should be easy to generate proper docs out of those.after the children & property schemas are done, one can also generate valid schema-hiccup out of a given registry 🙂
Is there a way to forbid some combination of keys in a map?
For instance a map with either :a
and :b
or :c
and :d
What do you mean by “using another predicate”?
(def my-schema
[:and
[:map
[:x int?]
[:y int?]]
[:fn (fn [{:keys [x y]}] (> x y))]])
I was just going to ask if there's a way to specify constraints on relations between keys which does not involve defining an ad-hoc function
Maybe using two separate closed maps also works. I think it depends on your domain perhaps?
Likely, although I was just thinking of a case like the one you illustrated, a constraint where the value at one key is greater than another
Something like:
(def preds {:> >, :>= >=, :< <, :<= <=, := =, :not= not=})
(defn -rel
[]
(m/-simple-schema
(fn [_ [pred a b]]
{:type :rel,
:pred (m/-safe-pred #((preds pred) (get % a) (get % b))),
:min 3,
:max 3})))
(m/validate
(m/schema
[:and
[:map
[:x :int]
[:y :int]]
[:rel :< :x :y]]
{:registry
(mr/composite-registry
m/default-registry
{:rel (-rel)})})
#_{:x 1 :y 2}
{:x 1 :y 1})
reminds me of jet :)
$ echo '{:a 1 :b 2}' | jet --query '(< :a :b)'
true
$ echo '{:a 1 :b 2}' | jet --query '(> :a :b)'
false
I think it’s good idea to experiment new relational schemas in the user space, both -simple-schema
& -collection-schma
are good ways to make these, like @ben.sless you demoed. The more stuff pushed into these data-languages, the more it looks like a simple sci.
I also had some thoughts on the subject here https://clojureverse.org/t/declarative-rules-for-relations-between-inputs/7623/5?u=bsless
A big plus it could have over sci is getting the benefit of the JIT while still keeping the schema serializable
but a lightweight map key dependency utility might be a good fit for many things. just (somone) needs (to invent) more syntax.
The schema serialization isn't my first (or third) priority here tbh, it's more about having more expressive schemas for the common 90% of cases and not having to define ad-hoc functions and errors for them, not to mention generators.
yeah, I think that's nice. I think clojure spec also doesn't have a good answer to this yet and it's pretty common
Is there an answer to this in TypeScript?
Does it help to define constraints on map keys
I'm surprised that TypeScript is sophisticated enough to be used as inspiration, I guess I need to learn more. I've presumed Elm/Haskell are more sophisticated, eg.: https://www.youtube.com/watch?v=IcgmSRJHu_8
Typescript is way more pragmatic than elm/Haskell. Structural typing almost looks like malli schemas enforced at compile time.
using modified ben’s syntax:
[:and
[:map
[:a :any]
[:b :any]
[:c :any]
[:d :any]]
[:rel
[:or
[:and :a :b]
[:and :c :d]]]]
The only objection I can come up with is that meander is too powerful, (i.e. the result will be hard to work with)
Maybe just [:and [:map ....] [:or [:required-keys :a :b] [:required-keys :c :d]] [:gtk :a :b]]
The rel syntax needs to be properly thought out, I just invented something ad-hoc to see if it could work
Then constraints unify by default (like datalog) or disjoin when specified (via :or
)
this works already:
(m/validate
[:schema {:registry {::a :int
::b :int
::c :int
::d :int}}
[:or
[:map ::a ::b]
[:map ::c ::d]]]
{::a 1, ::b 2})
; => true
But it doesn’t scale. When you have other set of constraints you’d need to write down all the combinations of valid keys.
[:constraints
[:or
[:and
[:requires :a :b]
[:> :a :b]]
[:and
[:requires :c :d]
[:< :c :d]]]]
but for rels between keys you could just have specialized ops like [:k> :a :b]
: the :a
key must be greater than the :b
key, without introducing some new concept
but question is: from whom the declaration are for? schema writer? user? external docs?
hm, yes, but as a schema not an argument (if we go back to the [:rel x y z]
or [:constraint ,,,]
suggestion
> What's wrong with just :> That it means different things in different contexts, this can be confusing imo.
What if you want to really do numeric comparison like [:> :a 5]
and 5 is also a key in a map?
The most shorthand constraint syntax I can think of would be :!
An alternative which conforms to your suggestion would be :!/>
you should always make a good trade-off between adding extra syntax (= complexity) and how much people are really going to use this
[:and
[:map
[:a :any]
[:b :any]
[:c :any]
[:d :any]]
[:keys/or
[:keys/and :a :b]
[:keys/and :c :d]]]
I was about to write the same thing
Then adding a predicate function on the keys would look like
[:and
[:map
[:a :any]
[:b :any]
[:c :any]
[:d :any]]
[:or
[:and
[:keys/and :a :b]
[:keys/< :a :b]]
[:keys/and :c :d]]]
The disjunction must not be about the keys
what does :keys/and
mean: both keys must be present? what does [:keys/and :a]
mean then? Maybe [:keys/req :a]
is a better name?
:keys/req
doesn’t convey the fact that :a
and :b
are related
I didn’t say I liked :keys/and
Maybe [:keys/present :a :b]
?
but you also didn't say what the semantic relationship between those keys is according to that op.
> :keys/req doesn’t convey the fact that :a and :b are related
:present
also doesn't communicate a relationship, just as :required
doesn't, it just means all those keys must be required/present
I was wrong. There is no relationship between the keys.
But somehow I find it weird to say “required” inside an or
condition
And now, where would we put a custom predicate [:fn …]
?
By the way, my use case is real for a project at work.
We are writing a Hbase driver
I guess you can already express required keys like this as well? [:and [:map ...] :a :b :c]
or do keywords not behave like predicates in malli?
And non informative, and useless for generation
(me/humanize (m/explain (m/schema [:and [:fn :a]]) {:b 1}))
;; => #:malli{:error ["unknown error"]}
The scan
function receives a map that could contain either :from
and :to
or :starts-with
There are not so many Clojure shops i Israel. Therefore, we could already say that 10% of Israeli Clojure shops use malli 😁
I’m struggling to write the schema of the map received by scan
[:map
[:starts-with :string]
[:from :string]
[:to :string]
[:xor
[:keys/req :starts-with]
[:keys/req :from :to]
[:keys/req :from]
[:keys/req :to]
]
]
:or
is not good in my case. It should be exclusive or
Because there are other keys
:time-range
, :limit
…
[:map
[:starts-with :string]
[:from :string]
[:to :string]
[:limit :number]
[:time-range [:map [:from-ms :int] [:to-ms :int]]
[:xor
[:keys/req :starts-with]
[:keys/req :from :to]
[:keys/req :from]
[:keys/req :to]
]
]
What is a :type
field?
In this case I'd start with some base schema, merge the different options with it and or between them
I don’t get what both of you suggested
@viebel I think this requirement is a bit weird:
:xor
[:keys/req :from :to]
[:keys/req :from]
if from is present, and to is not, then the second is true. but if to is present, then the first one is true. so to is optional.I did not look at the other requirements, but I think your logic can be "refactored"
I don’t see how to refactor the logic. All the followings are valid • :from 10 :to 20 • :form 10 • :to 20 • :starts-with aaa
The followings are invalid
• :from 10 :starts-with aaa • :to 20 :starts-with aaa • “from 10 :to 20 starts-with aaa
[:or [:map {:closed true}
[:from ...]
[:to ...]]
[:map {:closed true}
[:starts-with ...]]]
and if you have it open you leave open the possibility that :starts-with is in the map as well
[:or [:and [:map
[:from ...]
[:to ...]]
[:not [:map
[:starts-with ...]]]]
[:and [:map
[:starts-with ...]]
[:not [:map
[:from ...]
[:to ...]]]]
[:or [:and [:map
[:from ...]
[:to ...]]
[:not [:map
[:starts-with ...]]]]
[:and [:map
[:starts-with ...]]
[:not [:map [:from ...]]
[:not [:map [:to ...]]]]]
not that not exists, but conceptually thats what you would need to represent the constraint
if you are willing to explicitly enumerate all the keys you can just do the version with {:closed
I meant something like
(def Base
[:map
[:limit :number]
[:time-range [:map [:from-ms :int] [:to-ms :int]]]])
(def S1 (mu/merge Base [:map [:starts-with :string]]))
(def S2 (mu/merge Base [:map [:from :string] [:to :string]]))
Did some generalizing work, still not set on the syntax
(defn comparator-relation
[sym msg]
(let [f @(resolve sym)
type (keyword "!" (name sym))]
[type
(m/-simple-schema
(fn [_ [a b]]
(let [fa #(get % a)
fb #(get % b)]
{:type type
:pred (m/-safe-pred #(f (fa %) (fb %))),
:type-properties
{:error/fn
{:en (fn [{:keys [schema value]} _]
(str
"value at key "
a ", "
(fa value)
", should be "
msg
" value at key "
b
", "
(fb value)))}}
:min 2,
:max 2})))]))
(defn -comparator-relation-schemas
[]
(into
{}
(map (fn [[sym msg]] (comparator-relation sym msg)))
[['> "greater than"]
['>= "greater than or equal to"]
['= "equal to"]
['== "equal to"]
['<= "lesser than or equal to"]
['< "lesser than"]]))
(me/humanize
(m/explain
(m/schema
[:and
[:map
[:x :int]
[:y :int]]
[:!/> :x :y]]
{:registry
(mr/composite-registry
m/default-registry
(-comparator-relation-schemas))})
{:x 1 :y 1}))