This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-12-30
Channels
- # adventofcode (27)
- # ai (1)
- # announcements (2)
- # aws (66)
- # babashka (2)
- # beginners (34)
- # calva (28)
- # cider (5)
- # clj-kondo (18)
- # clojure (16)
- # clojure-europe (4)
- # clojure-norway (2)
- # clojure-uk (3)
- # clojurescript (11)
- # code-reviews (23)
- # conjure (23)
- # core-logic (1)
- # cursive (12)
- # datalevin (1)
- # datomic (9)
- # introduce-yourself (3)
- # kaocha (3)
- # klipse (4)
- # malli (42)
- # midje (1)
- # minecraft (1)
- # missionary (4)
- # music (1)
- # nextjournal (10)
- # polylith (5)
- # re-frame (2)
- # reitit (1)
- # releases (1)
- # sci (126)
- # shadow-cljs (4)
- # sql (2)
- # tools-deps (11)
One of the oldest (and annoying) issue is how to describe a map + map-of. Need to take a stab at it, here are the options:
1) ternary closed
[:map {:closed [:string :int]}
[:x :int]
[:y :int]]
2) new extra-keys
(or such)
[:map {:extra-keys [:string :int]}
[:x :int]
[:y :int]]
3) ::m/default
(like in :multi
)
[:map
[:x :int]
[:y :int]
[::m/default [:map-of :string :int]]]
leaning on 3, because:
• it’s coherent way to describe “default in case none of the defined keys matched”
• it’s easy to remove or add the key, e.g. (mu/assoc MyMap ::m/default [:map-of :uuid MyMap])
• :map-of
already supports key-decoding so things like :uuid
keys just work oob
comments welcome, original issue here: https://github.com/metosin/malli/issues/43
I think this example is confusing because the map of spec and the entries don't match.
A property of of
on the map makes the most sense imo
If its :string then it can't have a keyword key. It has to be a union and not a superset. Which is confusing
I read it “it’s a map with keyword keys :x
and :y
, the rest of the keys should be :string -> :int
. Same with plumatic:
{:x s/Int, :y s/Int, s/Str s/Int}
As a user who has to work and communicate with others via code, I wouldn't want this to be valid
I'd prefer that the specific keys will be a subset of the key schema and values be a subset of the value schema, not that the entire map describe a union
I see you point, would not want to use that myself, but that’s what JSON Schema, Plumatic and many others have atm.
5) wrap the extra keys with something like :schema
to mark it’s a schema, not a real key.
[:map
[:x :int]
[:y :int]
[[:schema :string] :int]]
yes, I don’t think there is a right answer to this, just compromises. Not a fan of those (the reason the issue has been open for so long)
Should map-of and map schemas be unified? One describes nominal tuples, the other a set of tuples
::m/default
makes sense, but using :map-of
together with that seems funny. In :map
schema the items are key-value pairs, :map-of
describes full map.
Something like [::m/default [:map-entry :string :int]]
or just [::m/default :string :int]
or [::m/default [:string :int]]
(just force the :map
default entry to always have two items) could be cleaner.
problem with [::m/default :string :int]
is that when you ask for m/children
of the map, you get funny results.
Maybe the :map-of
makes sense as there can be multiple "default" / extra keys
Maybe using ::m/extra
name would describe better that this is the schema for those keys that aren't directly defined in :map
schema
In JSON Schema -land:
{
"type": "object",
"properties": {
"number": { "type": "number" },
"street_name": { "type": "string" },
"street_type": { "enum": ["Street", "Avenue", "Boulevard"] }
},
"additionalProperties": { "type": "string" }
}
It reads to me like the specified properties and additional properties should have the same key type
the root need for this NOW btw is how to describe the namespaced keys in the destructuring syntax, elegantly. e.g. what is the schema for the map here:
(ns demo)
(let [{:keys [a1] ::keys [a2], :kikka/keys [a3]
:syms [b1] ::syms [b2] :kikka/syms [b3]
:strs [c1]
:or {a1 0} :as map}
{:a1 1, ::a2 2, :kikka/a3 3
'b1 4 'demo/b2 5, 'kikka/b3 6
"c1" 5}]
[a1 a2 a3 b1 b2 b3 c1])
; => [1 2 3 4 5 6 5]
thought that would be a good reason to add the “extra keys” here, but not sure if that is needed. could be just :multi
with dispatch on key qualification :thinking_face:
entry := qualified | simple | or | as
qualified := [ns/kind form]
simple := [kind form]
kind := keys | syms | strs
or := [:or map-of,,,]
as := [:as symbol]
unexpected help for the existing stuff:
(defn -map-like [x]
(or (map? x)
(and (seqable? x)
(every? (fn [e] (and (vector? e) (= 2 (count e)))) x))))
(defn -keys-syms-key [k]
(-> k name #{"keys" "syms"}))
(def MapLike
(m/-collection-schema
{:type :map-like
:empty {}
:pred -map-like}))
(m/parse
[MapLike
[:or
[:tuple [:= :keys] [:vector ident?]]
[:tuple [:= :strs] [:vector ident?]]
[:tuple [:= :syms] [:vector ident?]]
[:tuple [:= :or] [:map-of simple-symbol? any?]]
[:tuple [:= :as] symbol?]
[:tuple [:and :qualified-keyword [:fn -keys-syms-key]] [:vector ident?]]]]
'{:keys [b]
:strs [c]
:syms [d]
:demo/keys [e]
:demo/syms [f]
:or {b 0, d 0, f 0} :as map})
;{:keys [b]
; :strs [c]
; :syms [d]
; :demo/keys [e]
; :demo/syms [f]
; :or {b 0, d 0, f 0} :as map}
… not the most performant, but good for the destructuring case 🥳unexpected help for the existing stuff:
(defn -map-like [x]
(or (map? x)
(and (seqable? x)
(every? (fn [e] (and (vector? e) (= 2 (count e)))) x))))
(defn -keys-syms-key [k]
(-> k name #{"keys" "syms"}))
(def MapLike
(m/-collection-schema
{:type :map-like
:empty {}
:pred -map-like}))
(m/parse
[MapLike
[:or
[:tuple [:= :keys] [:vector ident?]]
[:tuple [:= :strs] [:vector ident?]]
[:tuple [:= :syms] [:vector ident?]]
[:tuple [:= :or] [:map-of simple-symbol? any?]]
[:tuple [:= :as] symbol?]
[:tuple [:and :qualified-keyword [:fn -keys-syms-key]] [:vector ident?]]]]
'{:keys [b]
:strs [c]
:syms [d]
:demo/keys [e]
:demo/syms [f]
:or {b 0, d 0, f 0} :as map})
;{:keys [b]
; :strs [c]
; :syms [d]
; :demo/keys [e]
; :demo/syms [f]
; :or {b 0, d 0, f 0} :as map}
… not the most performant, but good for the destructuring case 🥳