Right, I see things the way tbc++ does, but I'm wondering if I'm missing 
some key insights that make variants better.

I similarly struggle with the difference between (s/or) and (s/multi-spec).

If I were to implement the lispcast example today with spec I'd either use 
(s/or):

(s/def ::image-type #{:image/in-memory :image/on-disk :image/web})
(s/def ::pixels vector?)
(s/def ::filename string?)
(s/def ::url string?)
(s/def ::image-source
  (s/or :in-memory (s/keys :req-un [::image-type ::pixels])
        :on-disk (s/keys :req-un [::image-type ::filename])
        :web (s/keys :req-un [::image-type ::url])))

I could pair that with the case-of macro 
from https://github.com/lambdaisland/uniontypes to get some guarantees that 
I always handle all cases.

Or I would use (s/multi-spec):

(s/def ::image-type #{:image/in-memory :image/on-disk :image/web})
(defmulti image-type ::image-type)

(s/def ::pixels vector?)
(defmethod image-type :image/in-memory [_]
  (s/keys :req-un [::image-type
                   ::pixels]))

(s/def ::filename string?)
(defmethod image-type :image/on-disk [_]
  (s/keys :req-un [::image-type
                   ::filename]))

(s/def ::url string?)
(defmethod image-type :image/web [_]
  (s/keys :req-un [::image-type
                   ::url]))

(s/def ::image-source (s/multi-spec image-type ::image-type))

I'm not sure the practical difference between one or the other, but they 
both seem like they would cover the lispcast use case perfectly, where I 
now have a map like so:

{:type :image/in-memory
 :pixels [...]}
;; OR
{:type :image/on-disk
 :filename "/cats.jpg"}
;; OR
{:type :image/web
 :url "http://cats.com/cats.jpg"}

As all valid ::image-source. The spec restricts the possible state to only 
3 and I get a nicer map representation with named values.

So maybe variants were useful pre-spec? But even before spec, I still don't 
really get why they were any better? They just feel prone to the same human 
errors that ordered constructors are prone too. And ya, where my record has 
more then one value, what are you supposed to do?

This:
[[:image/name "img.png"]
 [:encode/width 42]
 [:encode/height 33]]

Or this:
[:image/name "img.png" 42 33]

Or this:
[:image/name ["img.png" 42 33]]

Or this:
[:image/name {:name "img.png"
                        :width 42
                        :height 33}]

??



On Tuesday, 22 August 2017 17:18:31 UTC-7, tbc++ wrote:
>
> Great, so these approaches are suggesting we wrap every value in a vector 
> or a hash map (as the lisp cast article does). That doesn't really help 
> much at all. Why is:
>
> [[:image/name "img.png"]
>  [:encode/width 42]
>  [:encode/height 33]]
>
> Any better than
>
> {:image/name "img.png"
>  :encode/width 42
>  :encode/height 33}
>
> Or perhaps people aren't suggesting vectors of vectors, I don't know. All 
> of it makes no sense to me when I look at in-production Clojure code. I 
> don't pass image names around as bare strings. I pass them around as part 
> of a hashmap where the keys give context to the strings, and also allow me 
> to pass in other data about the parameter. 
>
> Variants and sum types seem to come from OCaml and the like where strict 
> typing makes them much more needed. But in Clojure we prefer heterogeneous 
> collections, so I just don't understand why I would ever want variants. 
> They complicate the code for really no benefit. The complexity they are 
> attempting to solve can be solved by simply using maps and records instead 
> of bare values. 
>
>
>
> On Tue, Aug 22, 2017 at 5:43 PM, James Reeves <ja...@booleanknot.com 
> <javascript:>> wrote:
>
>> On 22 August 2017 at 23:04, Timothy Baldridge <tbald...@gmail.com 
>> <javascript:>> wrote:
>>
>>> I find the arguments for variants very unconvincing. As you stated with 
>>> specs and s/keys you can spec the same sort of data, and in a way that's 
>>> much more open-ended.
>>>
>>> For example:
>>>
>>> [:person {:name "Tim" :grade "B"}]
>>>
>>> What is the type of this data? We would probably guess a :person. But 
>>> what if we wanted to "cast" it to a :student? Well then we'd have to change 
>>> the variant tag to :student, but then it would no longer be a person, 
>>> unless we introduced some sort of inheritance that said all students are 
>>> people.
>>>
>>
>> I don't think that example is a representative use of variants in 
>> Clojure. More typically, variants are used like key/value pairings that 
>> exist outside a map. For example:
>>
>>   [:person/name "Tim"]
>>
>> If you actually have a map, the key/value pairs speak for themselves:
>>
>>   #:person{:name "Tim", :grade "B"}
>>
>> We can tell this is a "person" because it adheres to a particular shape, 
>> which can be specced out:
>>
>>   (s/def :person/student (s/keys :req [:person/name :person/grade]))
>>
>> But if you just have a key/value pairing, the value alone doesn't tell 
>> you much:
>>
>>   "Tim"
>>
>> Using a vector to represent a key/value seems to fit in well with how 
>> Clojure currently works:
>>
>>   (find m :person/name)
>>   => [:person/name "Tim"]
>>
>> But there's no "s/variant" or "s/entry" spec that's as convenient to use 
>> as s/keys.
>>
>> -- 
>> James Reeves
>> booleanknot.com
>>
>> -- 
>> You received this message because you are subscribed to the Google
>> Groups "Clojure" group.
>> To post to this group, send email to clo...@googlegroups.com 
>> <javascript:>
>> Note that posts from new members are moderated - please be patient with 
>> your first post.
>> To unsubscribe from this group, send email to
>> clojure+u...@googlegroups.com <javascript:>
>> 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+u...@googlegroups.com <javascript:>.
>> For more options, visit https://groups.google.com/d/optout.
>>
>
>
>
> -- 
> “One of the main causes of the fall of the Roman Empire was that–lacking 
> zero–they had no way to indicate successful termination of their C 
> programs.”
> (Robert Firth) 
>

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