Fork me on GitHub
#clojure-spec
<
2017-09-07
>
seantempesta12:09:36

Is there a version of s/or that just conforms the value and doesn’t tell you what value it is? I have a map where certain entries can be either a string or a function that returns a string and I just want to get back either a conformed map or :cljs.spec.alpha/invalid. Am I supposed to traverse the returned map and remove the named vectors?

misha13:09:27

@seantempesta

(s/def ::key (s/or :int int? :str string?))
(s/conform ::key 1)
=> [:int 1]
(s/conform (s/nonconforming ::key) 1)
=> 1
(s/conform (s/nonconforming ::key) :foo)
=> :clojure.spec.alpha/invalid

seantempesta13:09:53

@misha oh cool. so just call s/nonconforming first?

misha13:09:44

wrap spec you don't want vectors from in nonconforming

seantempesta13:09:28

Oh wait, s/nonconforming says that it returns the original value. I want the conformed value though.

misha13:09:14

what do you think original and conformed values are?

seantempesta13:09:33

original is the original value, but a conformer function can return a modified value.

misha14:09:19

if you have custom conformer, I think you can "avoid vector" there

seantempesta14:09:23

Well I was using all custom conformers. Writing separate functions like str-or-fn-ret-str, but I realized I was duplicating a lot of code. Also, I think that requires me to write custom generators for everything.

misha14:09:06

can you share str-or-fn-ret-str?

seantempesta14:09:30

It looks like (s/unform ::key (s/conform ::key val)) is working

seantempesta14:09:25

Well, it’s actually string-or-react-class. Here’s what it looks like:

(defn string-or-react-class?
  "Accepts either a string, react class, or a fn that returns a react class.  If it's a fn, props will automatically
  convert (js->clj) when the fn is called."
  [s-or-c]
  (cond
    (helpers/is-react-class? s-or-c) s-or-c
    (string? s-or-c) s-or-c
    (fn? s-or-c) (fn [props & children]
                   (let [clj-props (js->clj props :keywordize-keys true)
                         react-c (s-or-c clj-props children)]
                     react-c))
    :else :cljs.spec.alpha/invalid))

misha14:09:29

if you supply custom unformer - it might work. But I have a feeling you are doing something redundant

misha14:09:23

why do you have it as a conformer, rather than spec composed from predicates?

seantempesta14:09:47

That’s what I was asking. If I do it as s/or predicates I get back vectors

didibus05:09:30

From my perspective, you conform to get back a vector, or a custom projection of the specced data.

didibus05:09:02

If you want to know if something validates, you do (when (s/valid? ...) do), or one of the explain fns.

misha14:09:35

try to wrap s/or in nonconforming. this way you can have more granular conformer for just (fn? s-or-c) case.

seantempesta14:09:58

Okay. I’ll give that a shot. Thanks.

misha14:09:53

although I'd like to see examples of coercion in spec done right, myself

misha14:09:46

@seantempesta it seems like nonconforming or prevents any conformation inside its branches

bbrinck14:09:25

@seantempesta Can you talk more about why a call to conform is useful in this case? If you just want the original value, could you just do something like (if (s/valid? ::string-or-int x) x :cljs.spec.alpha/invalid)

misha14:09:03

I think to wrap fn? in (fn [props & children]

cgrand20:09:11

How do you prevent a :ret spec to be described by doc? I have a big spec and I’d like to be displayed by name rather than dumping it on the user. Currently I wrap it into spec/and. Is there a more elegant solution?

Alex Miller (Clojure team)21:09:08

then you’ll just see the name in the doc

cgrand21:09:51

I’ll stick with spec/and then. (btw I also abuse spec/and for late-bound aliases)

Alex Miller (Clojure team)21:09:14

I guess I don’t understand why that’s better

cgrand22:09:05

If I put it in only in the doc then it's not in the spec anymore. If I put it in doc and in spec it doesn't prevent doc from dumping the whole description. So s/and is the hack to have :ret spec'ed without doc being verbose

Alex Miller (Clojure team)22:09:53

sorry. I went and tried it and I understand what you’re saying now. So the current behavior is not the intention. there is a ticket for the early aliased spec resolution, don’t know the #.

ddellacosta21:09:10

what is the best approach for validating a tuple that has optional fields? E.g. [:required :required :optional]?

Alex Miller (Clojure team)21:09:48

use (s/cat :a int? :b int? :c (s/? int?)) etc

ddellacosta21:09:58

ah thanks Alex, s/? was the missing piece I was trying to find.

Alex Miller (Clojure team)21:09:19

it’s like regex - s/* s/+ s/? s/cat s/alt

misha21:09:08

@alexmiller can you comment on @seantempesta's question, please? what is a better way to do coercion with spec? what is expected way to work with conformed compound values (map conformed to some the nested spec)? e.g. when you get map values as an or-conform-vector [:branch conformed-value]. Is "conform-client" code supposed to know the spec structure, and be able to walk the conformed data structure?

Alex Miller (Clojure team)21:09:56

don’t do coercion with spec

Alex Miller (Clojure team)21:09:27

use conform to tell you how a value is parsed. then use clojure to transform that to what you want.

Alex Miller (Clojure team)21:09:59

for the specific case of wanting to avoid conforming an s/or (one of the most common), spec as (s/def :foo (s/nonconforming (s/or :a int? :b string?)))

Alex Miller (Clojure team)21:09:39

that is, use s/nonconforming at the leaves, not at the top

Alex Miller (Clojure team)21:09:17

for this particular case, I’ve come to believe that nonconforming or is a reasonable use case and either we should keep s/nonconforming or add that as a variant/option on or

misha21:09:11

so, going back to Sean's example, conform map to spec (will return [:fn? s-or-c] for some key), and then post-process conformed map with a coerce-function, which will replace [:fn? s-or-c] with (fn [props & children] ...?

Alex Miller (Clojure team)21:09:05

without having completely read or grokked all of the backchat, sure

Alex Miller (Clojure team)21:09:19

Clojure is like, pretty good at transforming data. use it.

misha21:09:08

I think the issue/question is: "should one wrap coerce-fn in s/conformer and conform to that, or should one just apply coerce-fn after conforming data to simpler spec?"

Alex Miller (Clojure team)22:09:44

if you do the former, you are registering specs that throw away information about the original value