Sean,
Thank you for sharing your input and code! Although I am interested by what
you shared about your team’s usage of spec, I don’t believe it addresses my
original problem of wanting to omit conformers when registering specs, yet
later opt in to use a conformer when *consuming* the specs.


------------------------------
Josh Tilles
[image: Signafire logo]
79 Madison Ave, 4th Floor
New York, New York 10016

Tel: (646) 685-8379 <+16466858379>
signafire.com <http://www.signafire.com/>
------------------------------

On Sat, Jan 14, 2017 at 8:03 PM, Sean Corfield <[email protected]> wrote:

> At World Singles, we use clojure.spec for that scenario: conforming /
> validating string input data and producing non-string conformed values.
>
>
>
> For each string-to-non-string type, we write a conformer that accepts the
> string and either produces the valid, parsed non-string valid or produces
> ::s/invalid. If we need further constraints on the non-string value, we use
> s/and to combine those.
>
>
>
> We also have generators for all of these: we use gen/fmap of a
> non-string-to-string formatter over a (custom) generator for the non-string
> values.
>
>
>
> Here’s an example, for string input representing dates:
>
>
>
> (defn coerce->date
>
>   "Given a string or date, produce a date, or throw an exception.
>
>   Low level utility used by spec predicates to accept either a
>
>   date or a string that can be converted to a date."
>
>   [s]
>
>   (if (instance? java.util.Date s)
>
>     s
>
>     (-> (tf/formatter t/utc "yyyy/MM/dd" "MM/dd/yyyy"
>
>                       "EEE MMM dd HH:mm:ss zzz yyyy")
>
>         (tf/parse s)
>
>         (tc/to-date))))
>
>
>
> (defn ->date
>
>   "Spec predicate: conform to Date else invalid."
>
>   [s]
>
>   (try (coerce->date s)
>
>        (catch Exception _ ::s/invalid)))
>
>
>
> (defmacro api-spec
>
>   "Given a coercion function and a predicate / spec, produce a
>
>   spec that accepts strings that can be coerced to a value that
>
>   satisfies the predicate / spec, and will also generate strings
>
>   that conform to the given spec."
>
>   [coerce str-or-spec & [spec]]
>
>   (let [[to-str spec] (if spec [str-or-spec spec] [str str-or-spec])]
>
>     `(s/with-gen (s/and (s/conformer ~coerce) ~spec)
>
>        (fn [] (g/fmap ~to-str (s/gen ~spec))))))
>
>
>
> (s/def ::dateofbirth (api-spec ->date #(dt/format-date % "MM/dd/yyyy")
> inst?))
>
>
>
> Sean Corfield -- (970) FOR-SEAN -- (904) 302-SEAN
> An Architect's View -- http://corfield.org/
>
> "If you're not annoying somebody, you're not really alive."
> -- Margaret Atwood
>
>
>
> On 1/13/17, 9:56 PM, "Josh Tilles" <[email protected] on behalf of
> [email protected]> wrote:
>
>
>
> Alex,
>
>
>
> Thank you for your reply! (I apologize for the delay in my own.)
>
>
>
> I’ll first address your final point (regarding what spec is buying me vs.
> just using normal functions): I’m still trying to figure that out myself!
> 😁 I.e., the boundaries of spec’s applicability/appropriateness are not
> yet apparent to me.
>
>
>
> Second, your suggestion of an explicit coercion step *before* using spec
> to validate the map indicates to me that we were envisioning different
> things, although perhaps I’ve misunderstood the role of spec’s conformers.
> I was thinking about a situation like processing maps of named timestamps
> that originated as JSON sent over HTTP. The maps’ JSON origins force the
> timestamp values to be strings, but it would be a lot more convenient for
> downstream processing if the timestamps were in a format more structured
> than a string, like a map (akin to how s/cat specs conform, perhaps with
> :year, :month and :day components) or even an org.joda.time.DateTime
> instance. Hence, in this hypothetical code, I’d like to ensure the strings
> nested in the maps look properly timestamp-y before preparing the data for
> downstream processing by converting the strings to the more-structured
> format. So the coercions I had in mind would make more sense as a *post*-step
> to validation, but does this seem like an inappropriate application of
> conformers to you? I’d thought that s/conform was meant for this kind of
> validate-then-convert behavior, but I see how coercion *before* validation
> would be valuable in a different way, enabling more-granular specification,
> with more-focused predicate implementations and more-precise error
> reporting.
>
>
>
> Ultimately, I’m trying to discern which of the following scenarios is the
> case:
>
> ·         I’m misusing spec by trying to “opt in” to using conformers on
> map values.
>
> ·         I’m using spec appropriately and the ability to specify
> conformance à la carte is a feature worthy of consideration.
>
> ·         I’m using spec appropriately and the inability to specify
> conformance à la carte is unfortunate yet tolerable & unlikely to change.
>
>
>
> From what you wrote before, it seems likely that either the first or the
> third is true, but I wanted to make sure I wasn’t being misunderstood first.
>
>
>
> Thank you,
>
> Josh
>
>
>
>
>
> On Dec 19, 2016, at 5:26 PM, Alex Miller <[email protected]> wrote:
>
>
>
> On Monday, December 19, 2016 at 2:42:49 PM UTC-6, Josh Tilles wrote:
>
> 1.     Is this seen as an acceptable tradeoff of an API that the core
> Clojure devs are otherwise happy with? Or is it in fact a *deliberate* 
> limitation,
> in line with the “map specs should be of keysets only”
> <http://clojure.org/about/spec#_map_specs_should_be_of_keysets_only>design
> decision?
>
> It is deliberate that s/keys works only on attributes and that you can't
> inline value specs.
>
> Right, and that’s a decision that I’m not contesting, btw.
>
>
>
> You could still do individual transformations by conforming non-registered
> specs if desired.
>
> Do you mean that I could s/conform maps against an “anonymous” spec like 
> (s/and
> ::something (s/conformer #(try (update % ::created timestamp->map) (catch
> SomeException _ ::s/invalid))))? Or are you referring to something else?
>
>
>
> Thank you,
>
> Josh
>
>
>
> P.S. To confirm my understanding that there is not a way to specify
> conformance inline, I attempted to create and use a spec defined like (s/keys
> :req [(s/and ::foo (s/conformer ,,,))]). To my surprise, clojure.spec
> used it without complaint, although the behavior of s/conform was
> unaffected. Should I create a JIRA issue for this?
>
>
>
> Right now anything that's not a keyword gets filtered out and ignored. I'd
> be in favor of erroring on other stuff, but not sure if that's at odds with
> how Rich thinks about it. So, could do an enhancement jira if you wish.
>
> OK, I’ll look into doing that this weekend.
>
>
>
> P.P.S. A previous draft of this email had detailed illustrative examples,
> cut out of fear of being tiresomely long-winded. If anything I described
> isn’t clear, let me know and I’ll go ahead and include the examples in the
> conversation.
>
>
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to [email protected]
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> [email protected]
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google Groups
> "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> For more options, visit https://groups.google.com/d/optout.
>
>
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to [email protected]
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> [email protected]
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google Groups
> "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> For more options, visit https://groups.google.com/d/optout.
>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to [email protected]
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> [email protected]
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google Groups
> "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to