This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-12-05
Channels
- # adventofcode (41)
- # bangalore-clj (4)
- # beginners (283)
- # boot (62)
- # clara (9)
- # cljsrn (3)
- # clojure (112)
- # clojure-brasil (1)
- # clojure-greece (1)
- # clojure-korea (6)
- # clojure-russia (99)
- # clojure-spec (29)
- # clojure-uk (12)
- # clojurescript (34)
- # clojurex (5)
- # core-logic (1)
- # cursive (31)
- # datomic (30)
- # devcards (5)
- # editors (19)
- # emacs (31)
- # events (5)
- # garden (4)
- # hoplon (137)
- # lein-figwheel (3)
- # luminus (4)
- # mount (7)
- # off-topic (7)
- # om (18)
- # om-next (3)
- # onyx (88)
- # proton (1)
- # protorepl (6)
- # re-frame (48)
- # reagent (15)
- # spacemacs (41)
- # testing (1)
- # untangled (2)
- # yada (18)
Finally have a good example of an issue I keep running into with s/keys
. Here is a polymorphic payload for a couple of websocket messages:
{:msg/id :foo/msg, :msg/data { ... foo/data ... }}
{:msg/id :bar/msg, :msg/data { ... bar/data ... }}
My basic intuition is to have a multi-spec that dispatches on :msg/id
:
(defmulti msg-spec :msg/id)
But then I have a problem, because methods need to deal with a polymorphic :msg/data
too:
(defmethod msg-spec :foo/msg [_] (s/keys :req [:msg/data !?]))
(defmethod msg-spec :bar/msg [_] (s/keys :req [:msg/data !?]))
All I can think of is to have the multi-spec on :msg/data
instead. But that dispatch function will be much more complex and will need to infer, from the :msg/data
alone, the value that was right there in :msg/id
.
Am I missing something?You can base your polymorphism on more than just a key - it's an arbitrary function
So you could base it on both id and something in msg
Or you could do more than one level of multispec
Right, I think I understand that and it seems to be the problem. The :msg/id
is all I need to determine the type of :msg/data
but it’s only available at that root level
With a multi-spec for :msg-data
I’d need to look at what keys are in there, and in some cases what values, just to derive a tag was just there in the parent map
I've heard Rich's rebuttal of this kind of "contextual polymorphism" , but this example feels like something that will come up in practice, especially as people adopt qualified keywords in their apis. An ad-hoc binding of a map-key to a spec would be a bulletproof one-liner alternative to what would now have to be a complex, possibly buggy dispatch function.
So are you saying that :mag/data has more than one spec? That seems wrong.
Yes indeed. The websocket library is embedding the msg/tag and the msg/data in its payload. It takes a [msg/tag msg/data]
and we get this map.
So we have multiple specs for both the msg itself (omitted other keys that need specs) and the msg/data, both of which ultimately depend on the msg/tag.
Curious what’s your intuition on why this is wrong? This is something that comes up a lot in my experience whit what are now unqualified library apis, but it seems bound to happen more as people adopt namespaced kws?
In a nutshell, I can define that :msg/id
is either a :foo/msg
or a :bar/msg
and that :msg/data
is either a ::foo/spec
or ::bar/spec
but I can’t enforce that relationship at the msg level
Qualified names should have meaningful stable semantics. Using qualified names should make this happen less if people are using sufficiently qualified names (which they should)
Could you not wrap an s/and around specs on both of these to add a constraint?
Are ::foo/spec and ::bar/spec just s/keys specs? If so, do they really need to be different or is just (s/keys) to validate all attrs sufficient?
lvh: it was not part of the initial impl. it’s a highly rated request in the jira system and Rich mentioned it as an idea to me long ago. it’s not obvious to me how to best implement it (where to put the meta) for all types of specs (when you consider things like (s/def ::a ::b) as kws don’t have meta). Do you have a var to add meta to?
jfntn: it would help me to see a more detailed example
cool, I will be in and out today
alexmiller: Sometimes I do by accident, yes — but much to your point; there’s no real vars in spec most of the time; so presumably that’s not where tooling would want to go look
=> (s/conform
(s/+ (s/cat :one (s/+ #{1 2 3})
:alpha (s/? #{:a})))
[1 2 :a 2])
[{:one [1 2], :alpha :a} {:one [2]}]
=> (s/conform
(s/+ (s/cat :one (s/+ #{1 2 3})
:alpha (s/? #{:a})))
[1 2 :a 2 3])
[{:one [1 2], :alpha :a} [{:one [2 3]}]]
=> (s/conform
(s/+ (s/cat :one (s/+ #{1 2 3}) :alpha (s/? #{:a})))
[1 2 :a 2 3 :a 2 3 :a 2])
[{:one [1 2], :alpha :a} [{:one [2 3], :alpha :a} {:one [2 3], :alpha :a} {:one [2]}]]
This is a known bug