I don't use schema/core.typed much in my actual projects, while I have done
many attempts it just never worked out. I like the idea and should
definitely use them more but it is just too much of a moving system and not
stable enough yet (eg. didn't even know s/either is deprecated).
If you look at these implementations
(def OneOff [(s/one s/Str 'name) (s/one s/Str 'email)])
(s/defrecord OneOff
[name :- s/Str
email :- s/Str])
(defrecord OneOff [name email])
All of them do more or less the same thing, just different. Clojure has
really good support for records and they feel natural to me. I don't need
to remember that :email is the second field. So I can do (:email one-off)
instead of (get one-off 1). Remembering the positions can get tedious and
very error prone over time. Always remember that software evolves over time.
(:email one-off) still works if my data changes to (defrecord OneOff [name
note email]), the vector version doesn't. Referring to things by name is a
good thing.
I do not know Elixir but in Erlang you very rarely use tuples for actual
data, usually just messages and return values. Data is all Records, maybe
maps these days but I left before R15B so can't say.
I usually only do data validation on the system boundary and trust in it
after. Otherwise you might end up validating the same data over and over
again. So if I get something from a user (eg. HTTP) I validate that it is
what I expect and transform if needed. I have an explicit (if
(is-this-what-i-expect? data) ...) and not something hidden in a {:pre ...}
or some macro magic. I expect the data to be wrong (never trust the user)
and want to test that assumption ASAP. I don't like to use exception for
validation errors. Writing validation functions is not fun but it is very
simple.
YMMV, do what feels right.
Keep it simple.
/thomas
On Tue, Sep 8, 2015 at 4:42 AM, Amith George <[email protected]> wrote:
> >> I probably wouldn't use protocols since I doubt there is a function
> signature that is exactly identical for all branches. Each branch probably
> needs access to different parts of your system (eg. database) and always
> passing everything to everything is not ideal.
>
> >> Multi-Method is great if you want something openly extensible but that
> again seems unlikely here and also assumes that everything requires the
> same arguments.
>
> I had the same concerns and wanted to use a simple function with
> pattern/condition matching to dispatch to the appropriate function. I had
> considered using Records as synonymous with using polymorphic calls (multi
> methods, protocols). Its good to know the alarm bells ringing in my head
> had merit :). Thanks for that.
>
> Thinking out loud, if we are not using Records for polymorphism, are we
> using it to guarantee structure? If you could indulge me a little more,
> consider the following two implementations.
>
>
> (def OneOff [(s/one s/Str 'name) (s/one s/Str 'email)])
>
> (defn send-one-off
> [something thing [name email :as data]]
> {:pre [(s/validate OneOff data)]}
> ,,,)
>
> (defn send
> [app thing recipient]
> (match [recipient]
> [[:one-off & data]] (send-one-off (:something app) thing data)))
>
>
>
>
> Individual schema are created for each variant case and are checked
> against by the respective functions. The dispatch function only needs to
> know how to check for individual cases of the variant.
>
>
> (def OneOffMap {:type (s/eq :one-off) :name s/Str :email s/Str})
> (def ExistingContactMap {:type (s/eq :existing) :contact-id s/Int})
>
> (def Recipient (s/either ExistingContactMap OneOffMap))
> ;; s/either is deprecated and s/cond-pre is recommended
> ;; however, validating valid OneOffMap data using the following
> ;; still throws an error.
> ;; (def Recipient (s/cond-pre ExistingContactMap OneOffMap))
>
> (defn send-one-off
> [something thing {:keys [name email] :as data}]
> ,,,)
>
> (defn send
> [app thing recipient]
> {:pre [(s/validate Recipient recipient)]}
> (match [(:type recipient)]
> [:one-off] (send-one-off (:something app) thing recipient)))
>
> This reverts to using a map with a :type key. Individuals schema for each
> :type value. A combined schema to represent a recipient. The dispatch
> function only needs to know about the existence of the :type key and the
> values it can handle.
>
>
> Whether we use records or maps or variants, the dispatch function needs to
> know what contract is implemented by recipient. Whether it will be an
> instance of something, or a variant or a map with type keys. Neither
> versions care for any other data present in recipient or its structure.
>
> At this point I am confused, what makes one version better than the other.
> Creating schema definitions for the records seemed a lot easier than for
> the other two. Also I am assuming that if records are created using
> s/defrecord, the factory functions (->, map->) will automatically validate
> them?
>
> I really appreciate you taking the time to clarify things for me.
>
> --
> Amith
>
>
>
> On Monday, 7 September 2015 20:57:46 UTC+5:30, Thomas Heller wrote:
>>
>>
>>> (def Recipient
>>>> (s/either PlaceHolder
>>>> Existing
>>>> OneOff))
>>>>
>>>
>>> This looks interesting. Where would I actually use this? I mean, if I
>>> have created three records, I may as well implement multi methods or
>>> protocols, right? Even if I don't do those, I will still need to use
>>> `(condp instance? obj ...)` or equivalent to select the appropriate branch
>>> for processing. Is there a way I can use Recipient to select a branch?
>>>
>>
>> I probably wouldn't use protocols since I doubt there is a function
>> signature that is exactly identical for all branches. Each branch probably
>> needs access to different parts of your system (eg. database) and always
>> passing everything to everything is not ideal.
>>
>> Multi-Method is great if you want something openly extensible but that
>> again seems unlikely here and also assumes that everything requires the
>> same arguments.
>>
>> cond(p) sounds perfect for this case. I tend to write each branch as a
>> single function and keep the dispatch as compact as possible.
>>
>> (defn send-placeholder [thing {:keys [text]}]
>> ...)
>>
>> (defn send-existing [db thing {:keys [contact-id]}]
>> ...)
>>
>> (defn send-one-off [something thing {:keys [name email]}]
>> ...)
>>
>> (defn send [app thing recipient]
>> (condp instance? recipient
>> PlaceHolder
>> (send-placeholder thing recipient)
>> Existing
>> (send-existing (:db app) thing recipient)
>> OneOff
>> (send-one-off (:something app) thing recipient)))
>>
>>
>> That greatly reduces the cognitive load when looking at each separate
>> implementation and also keeps the actual internal structure of the
>> Recipient out of the dispatch. The conpd does not need to know how many
>> fields are in OneOff, the tuple/vector/variant match versions must know
>> that.
>>
>> /thomas
>>
>>
>>
>>
>>
>>
>> --
> 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 a topic in the
> Google Groups "Clojure" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/clojure/cuHfhVVE2zg/unsubscribe.
> To unsubscribe from this group and all its topics, 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.