Fork me on GitHub
#clojure-spec
<
2018-12-22
>
dustingetz15:12:28

How do I spec inside a reagent reaction or other container type that isn't a seq

dustingetz15:12:55

I just googled and found https://stackoverflow.com/questions/37972074/how-to-clojure-spec-a-reference-type-like-atom which says "Don't", okay i get it but that is not compatible with performant UI programming

dustingetz15:12:32

Has this been debated before?

didibus16:12:20

Well, you're not supposed to spec everything

didibus16:12:36

The same way you don't necessarily put comments on everything

didibus16:12:49

You need to ask, is this hard to understand, do people wonder what the shape and structure of this thing is, if so, a spec can be a great way to make that easier

didibus16:12:40

Or, if you specifically want to perform generative tests on a particular function that would benefit from it

didibus16:12:30

Or if you need to validate user input, or validate data you're about to persist, etc.

didibus16:12:09

But if its obvious what the things are, you don't need a spec.

didibus16:12:07

Now to spec an atom, I mean, specs are just predicates. You can easilly do: #(s/int? @%)

didibus16:12:01

Now you do have to make sure that the deref won't trigger unintended effects

didibus16:12:49

Like in the case of a future or delay

didibus16:12:11

And for atom, the other challenge is mutation. Your spec needs to either be the union of all possible valid values the atom will ever contain, throughout its many mutations

didibus16:12:13

Or it has to be more generic, like validates that you expect the thing to be an atom and that's all

didibus16:12:49

Or you need a way to know the particular context and time and what in that context and time the atom is supposed to contain

didibus16:12:07

And spec relative to that

didibus16:12:48

The validator idea is good also. Validators were designed to validate the data being set on an atom. So you can leverage spec to make the validation.

didibus16:12:59

Since spec is pretty much a DSL for easy validation

dustingetz17:12:06

It's the ratom which is the incidental complexity, it is spiritually just data. For better or worse, Reagent UIs pass things that are data as reactions and it is what it is. But I was unable to get it to work in a nontrivial case

didibus23:12:02

Can you show an example? Also, the ratom isn't a normal atom, and its reactive nature has a different hook for when the data would be updated and need to be validated. At least from what I know, I actually havn't used reagent

tedcushman21:12:19

I have encountered errors when trying to use s/and with s/or as the first argument:

=> (s/explain
     (s/and (s/or :int int? :double double?)
            pos?)
     3)
ClassCastException clojure.lang.MapEntry cannot be cast to java.base/java.lang.Number  clojure.lang.Numbers.isPos (Numbers.java:96)

tedcushman21:12:26

Using s/or after the first argument appears to work though:

(s/explain
  (s/and pos?
         (s/or :int int? :double double?))
  3)
Success!

tedcushman21:12:06

Unfortunately, if you are trying to write (s/and (s/or ...) (s/or ...)) there doesn’t seem to be a workaround.

alexmiller21:12:41

s/and will flow the conformed result so the and will be receiving a value like [:int pos?]

👍 4
alexmiller21:12:15

prob the best option here is to (s/or :int (s/and int? pos?) :double (s/and double? pos?))

tedcushman22:12:41

ok, now that I read the doc, I see the part about “successively conformed values”.