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 <strider...@gmail.com> 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 clojure@googlegroups.com
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com
> 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
> clojure+unsubscr...@googlegroups.com.
> 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 clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
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 clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to