Fork me on GitHub
#clojure-spec
<
2016-08-07
>
levitanong02:08:21

@seancorfield: thanks for replying :) so you're saying that I should multi spec the message as a whole? If that is so, then how would I change the spec for the :message/payload for each arm of the multi spec? Is there a way to alias a spec locally? Like if I have two different specs, ::warning-payload and a ::user-joined-payload, how would I be able to assign ::warning-payload to :message/payload inside the (defmulti message :warning ...) body, and likewise for the :user-joined branch?

seancorfield02:08:41

I haven't looked at the multi-spec stuff. That's just what my intuition said ought to work...

seancorfield02:08:41

Based on reading the documentation just now, it looks like you could have each method return an s/and of the message spec (with payload just specified as map? perhaps), along with a type-specific predicate for the payload value.

seancorfield03:08:08

I'm on my phone otherwise I'd crack open a REPL and try to produce a working spec for you.

levitanong03:08:38

Hmm… Wouldn’t that just end up spec’ing a message against both the message spec and the payload spec?

levitanong03:08:32

oh wait, I think I see what you’re saying

seancorfield05:08:33

Yup, that's along the lines of what I was thinking. I don't know how it would hold up for generative testing -- I suspect you'd need to provide s/with-gen based on the ::pin-with-latlng spec -- but it seems reasonable for validation.

levitanong05:08:55

I haven’t even gotten to generative testing yet 😛

levitanong05:08:38

I hope rich hickey adds a way to decouple spec map keys from the specs of the keys themselves. I imagine I won’t be alone with this use case.

levitanong05:08:47

i.e. something like (s/def some-map-spec (s/keys :req [:some/key1 :some/key2] :req-dec {:some/key3 :spec-for-key-3}))

seancorfield05:08:51

Well, that's why you use namespaced keys -- even for non-qualified keys.

seancorfield05:08:39

I think that if you have :foo/bar that's a very specific entity in your application, but :bar is not.

levitanong05:08:42

the predicate with s/valid? works, but it seems hacky

seancorfield05:08:13

So you can use :quux/bar and :what/bar as different specs for :bar in different contexts

levitanong05:08:30

hmm. would you say then that I’m being too specific with how i’m namespacing the :message/payload?

seancorfield05:08:07

If you're using a qualified keyword, it should represent one very specific entity in your domain.

seancorfield05:08:37

We have :ws.domain.member as a prefix for attributes that belong to "Member" entities.

seancorfield05:08:46

so things are unique in our domain

seancorfield05:08:53

:message/payload is not very specific

seancorfield06:08:18

It's interesting... clojure.spec is very opinionated. And it speaks to how the Clojure/core team feel the language should be used.

seancorfield06:08:47

And if you use it that way, life is easy. If you fight it, life is hard.

seancorfield06:08:14

As a guideline, any time I find something feels "hard" in Clojure, I ask here what I'm doing wrong (because it nearly always means I am doing something wrong). And the correct idiom is always easier...

levitanong06:08:58

Any hint on what I’m doing wrong? 😄

levitanong06:08:26

Could it be that i’m structuring the shape of my messages wrong?

seancorfield06:08:29

I don't know that you're doing anything wrong but you might consider whether :message is the right prefix for your keywords... Is that specific enough?

seancorfield06:08:51

Why is your payload nested?

seancorfield06:08:18

Having it at the top-level feels a better fit

seancorfield06:08:02

:message/uuid, :message/action are common and then the other keys would be dependent on the action -- why nest it?

levitanong06:08:50

The idea is to have it as a generic way to pass information between clients and the server through a websocket. The sender of the message identifies what it would like the recipient to do with the information provided. Sometimes, a payload is not necessary for some specific actions

seancorfield06:08:46

Is the uuid an inherent part of the data itself? Or is it part of some wrapper concept?

levitanong06:08:59

Sure, I could bring everything to the top level, but it feels neater to structure it this way. So that everything on the top level is something to do with the message’s metadata: its uuid, who sent it, the timestamp, what action should be done, etc… And then the payload is just a whatever context is needed to perform the action.

levitanong06:08:34

the UUID is part of the wrapper. It’s a way to facilitate some optimistic update error handling.

levitanong06:08:00

or some other debugging purpose

seancorfield06:08:25

If you have a dynamic payload, the discriminant has to be part of that, otherwise it isn't semantically consistent

seancorfield06:08:40

That's why I suggested considering the message as a whole.

levitanong06:08:40

So you mean it should be :payload/action?

levitanong06:08:49

ah, or bringing everything up to the message

seancorfield06:08:03

If it's really UUID + message then action should be part of the message

seancorfield06:08:23

It depends on whether the message means something independent of the UUID.

seancorfield06:08:52

(although maps are open so adding a UUID is OK anyway)

seancorfield06:08:00

The real issue is that moving action outside payload makes it impossible to analyze payload on its own -- it has no type

levitanong06:08:49

As an aside: > If you have a dynamic payload, the discriminant has to be part of that, otherwise it isn't semantically consistent > The real issue is that moving action outside payload makes it impossible to analyze payload on its own -- it has no type These seem like very important concepts to learn. Is there a resource where one learns things like this?

seancorfield06:08:13

I don't know... it just seems... intuitive...?

seancorfield06:08:58

Not helpful, I know...

seancorfield06:08:10

...but what is the atomic element here?

levitanong06:08:56

I do suppose that the truly important parts here are the :message/action and the contents of :message/payload.

seancorfield06:08:29

So "payload" is important and it must have an inherent discriminant (i.e., action must be part of it)

seancorfield06:08:46

And message is uuid + payload

levitanong06:08:21

okay yeah it makes a lot of sense now

levitanong06:08:57

And with that framework, if I were to rename the keys, I would rename payload to action, and action to type.

seancorfield06:08:50

Well, "payload" would be a map that contained "action" (as a type / discriminant) and a various keys that depend on the action ... and then it's an open question whether your "message" is uuid + payload map or whether it's a payload map with a :uuid key added

seancorfield06:08:03

(i.e., nested or not)

levitanong06:08:07

Okay, I think I have enough information to proceed confidently. 😄

levitanong06:08:20

Thanks so much, @seancorfield!

seancorfield06:08:01

Glad that was helpful... sometimes I'm not sure I'm being helpful 🙂

levitanong06:08:58

I’m sure you always are 😄 cheers! 🍻

levitanong06:08:06

(helpful, I mean)

seancorfield06:08:33

I'll raise a Snake Dog IPA to your 🍻 🙂

gfredericks16:08:50

okay I'm going to try to slog through a schpec release today

lvh16:08:35

@gfredericks: my wife seems bent on getting me to Portage Park (& assoc’d pool) today so you might have a window betwixt merging xor and me putting up weighted-or 😉

gfredericks16:08:13

making two releases isn't terrible either

lvh16:08:50

sure 😄

gfredericks21:08:01

I'm thinking putting a test runner in schpec

gfredericks21:08:24

it would allow you to add metadata to functions you want tested, where you could also set up stubs/mocks/whatever

gfredericks21:08:39

so theoretically you wouldn't need a separate test namespace if you didn't have other tests

gfredericks21:08:56

is that silly or dumb?