Fork me on GitHub
#clojure-spec
<
2020-06-09
>
dev-hartmann15:06:23

hey folks, excuse my noobiness, but is it possible to return a s/def from a function?

dev-hartmann15:06:54

I'm trying to build them dynamically from a parsed json which is working okish to the point where I try to return them

alexmiller15:06:06

s/def is a function which I don't think you mean. do you mean a spec form or a spec object?

dev-hartmann17:06:38

Ah, yes. Sry, that’s what I mean. I want to create them dynamically, save their names to an atom and then do an s/def

alexmiller18:06:21

one way you could do this is to directly manipulate the registry (as s/def's impl does). in spec 2 we've pulled out a non macro function s/register for this purpose.

dev.4openID21:06:16

(defn len [n] (and (< (count n) 6)
                   string?))

(s/def ::piece-IDs (and vector?
                       (s/coll-of len into [])))

(s/valid? ::piece-IDs ["92257" "12" "01234"] );; => true
(s/conform ::piece-IDs ["92257" "12" "01234"]) ;; => ["92257" "12" "01234"]
(s/explain-str ::piece-IDs ["92257" "12" "01234"]) ;; => "Success!\n"

In the above code the s/def is valid, however it depends on the defn provided - seems untidy and not best practice


Can this not be changed to incorporate the count condition soemwhat as below?
(obvs. the coud does not work as the count does not apply to the strings)

(s/def ::piece-IDs (and vector?
                        (s/coll-of #(and (< count 0)
                                         string?) into [])))
Any ideas to eliminate the defn in the above case?

alexmiller21:06:16

you don't need a custom predicate for that at all

vlaaad21:06:52

(s/valid? (s/coll-of string? :kind vector? :max-count 5) ["1" "2" "3" "4" "5" "6"])

alexmiller21:06:53

coll-of has a :max-count modifier

alexmiller21:06:03

also note that in spec 2, there is now a catv too that would slim this down to just (s/catv string? :max-count 5)

dev.4openID22:06:22

Hi, @vlaaad and @alexmiller (s/def ::pieceIds (s/coll-of string? :kind vector? :max-count 5)) (s/valid? ::pieceIds ["JD014600007821392257" "12" "01234567890123456789" "777777777" "66666666666666666666666666666" "77"]) ;; => false it is counting 6 strings in vector (s/conform ::pieceIds ["JD014600007821392257" "12" "11111111111111112222222222222222222y"]) ;; => ["JD014600007821392257" "12" "11111111111111112222222222222222222y"] (s/explain-str ::pieceIds ["JD014600007821392257" "12" "11111111111111112222222222222222222y"]) ;; => "Success!\n" it is not applying the string length limit of 5 chars produces the count how many string there are; whereas the code I presented determines whether every string length is less than 6 (this being the challenge I find myself in) Subtle difference to what I suggested. Any ideas? BTW Alex: I am using leiningen so I am not sure how to exactly refer spec 2; i.e. I do not use an edn file per se. Perhaps some guidance on that would be helpful - It refers to git etc. so I am unsure

seancorfield22:06:59

@dev.4openid Do you want a :min-count as well as a :max-count?

seancorfield22:06:34

Oh, you want the strings themselves to be checked for length, not the vector?

seancorfield22:06:28

So you want (s/def ::short-string (s/and string? #(>= 5 (count %)))) and then (s/coll-of ::short-string ...)

seancorfield22:06:09

:min-count and :max-count apply to s/coll-of, not to things inside the collection.

dev.4openID22:06:41

Well, it looks like I had the general idea right 😉 but not the implementation. I will try now

dev.4openID23:06:42

@seancorfield No it must be missing something

(s/def ::short-string (and #(>= 5 (count %))
                    string?))

(s/def ::piece-IDs (s/coll-of ::short-string into []))

(s/valid? ::piece-IDs ["92257" "12" "012344444"] );; => true NOT
(s/conform ::piece-IDs ["926257" "12" "01234"]) ;; => ["92257" "12" "01234"] 
(s/explain-str ::piece-IDs ["9992257" "12" "01234"]) ;; => "Success!\n"

seancorfield23:06:23

Read what I suggested for ::short-string and compare it to what you wrote.

seancorfield23:06:14

(I just edited mine BTW)

seancorfield23:06:39

(and #(..) string?) is truthy

seancorfield23:06:37

(s/and #(..) string?) is a spec

seancorfield23:06:59

Also, it's safer to add the type predicate (e.g., string?) first before applying count otherwise this will blow up (s/valid? ::piece-IDs [42]) because it will try to call (count 42) before testing it is a string.

dev.4openID23:06:11

Yep, I missed it 😠 now it works and I have to discipline myself on the s/. Bit of improvement work!! @seancorfield thanks for the help. Will use string (type checks first) Thx

seancorfield23:06:21

(somewhat ironically, my incorrect version -- (and string? #(>= 5 (count %))) -- worked by accident because it evaluated to just #(>= 5 (count %)) because (and truthy x) => x for any x

seancorfield23:06:11

I only realized my version was broken when I tried the 42 example and mine blew up, even tho' it had correctly rejected your test example with "012344444"

dev.4openID23:06:57

I am learning this language ad enjoying it. this is after the last serious coding I did 30 years ago. It is a steep curve, it feels like doing "maths" all over again - strict discipline and be aware of the subtilties . There are so many variations and tweaks I sometimes get lost! Too easy too make mistakes. Worth learning as it is pretty powerful. Thx for your help

seancorfield23:06:23

@dev.4openid The REPL is your friend here. If you try out every single function as you write it, you might have caught the problem with your len function sooner:

user=> (defn len [n] (and (< (count n) 6)
                   string?))
#'user/len
user=> (len "123")
#object[clojure.core$string_QMARK___5410 0x3bc735b3 "[email protected]"]
user=>

seancorfield23:06:54

and:

user=> (len 42)
Execution error (UnsupportedOperationException) at user/len (REPL:1).
count not supported on this type: Long
user=>