clojure-spec

About: http://clojure.org/about/spec Guide: http://clojure.org/guides/spec API: https://clojure.github.io/spec.alpha/clojure.spec.alpha-api.html
2020-06-09T15:28:23.219600Z

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

2020-06-09T15:28:54.220300Z

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

alexmiller 2020-06-09T15:35:06.221100Z

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

2020-06-09T17:21:38.222500Z

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

alexmiller 2020-06-09T18:09:21.225200Z

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.4openID 2020-06-09T21:23:16.227100Z

(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?

alexmiller 2020-06-09T21:46:16.227400Z

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

vlaaad 2020-06-09T21:46:52.228Z

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

alexmiller 2020-06-09T21:46:53.228100Z

coll-of has a :max-count modifier

alexmiller 2020-06-09T21:48:03.229Z

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.4openID 2020-06-09T22:24:22.233200Z

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

seancorfield 2020-06-09T22:49:59.233900Z

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

seancorfield 2020-06-09T22:50:34.234400Z

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

dev.4openID 2020-06-09T22:50:54.234900Z

Yes

seancorfield 2020-06-09T22:51:28.235600Z

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

seancorfield 2020-06-09T22:52:09.236200Z

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

dev.4openID 2020-06-09T22:53:41.237200Z

Well, it looks like I had the general idea right ๐Ÿ˜‰ but not the implementation. I will try now

dev.4openID 2020-06-09T23:05:42.238Z

@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"

seancorfield 2020-06-09T23:07:23.238400Z

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

seancorfield 2020-06-09T23:09:14.238700Z

(I just edited mine BTW)

seancorfield 2020-06-09T23:09:39.239100Z

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

seancorfield 2020-06-09T23:10:37.239500Z

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

seancorfield 2020-06-09T23:11:59.241Z

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.4openID 2020-06-09T23:14:11.242500Z

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

seancorfield 2020-06-09T23:15:21.243700Z

(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

seancorfield 2020-06-09T23:16:11.244700Z

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.4openID 2020-06-09T23:29:57.248500Z

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

seancorfield 2020-06-09T23:40:23.249500Z

@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 "clojure.core$string_QMARK___5410@3bc735b3"]
user=>

seancorfield 2020-06-09T23:41:54.250100Z

and:

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