I'm reading on http://www.lispcast.com/reduce-complexity-with-variants and have seen Jeanine's talk on her encoding scheme for sum types, but for some reason, I'm not convinced of the benefit over a tagged record encoding. I see that the positional vector or hash-map suggested in the lispcast article has a theoretically smaller number of states, but because it lacks names, it seems at a higher risk of human error. Anyone has thoughts on this? And now that we have specs, I feel like a multi-spec would address concerns of the tagged record encoding by being able to validate that the correct set of keys exist for the given type, while also having the benefit of named fields. What do others think?
--
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
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscribe@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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
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.
--
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
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscribe@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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
(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])))(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)){:type :image/in-memory :pixels [...]};; OR{:type :image/on-disk :filename "/cats.jpg"};; OR{:type :image/web :url "http://cats.com/cats.jpg"}[[:image/name "img.png"] [:encode/width 42] [:encode/height 33]][:image/name "img.png" 42 33][:image/name ["img.png" 42 33]][:image/name {:name "img.png"
:width 42
:height 33}]
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.
(s/def ::temp (s/or :one string? :two int?))=> (s/conform ::temp "one-two")[:one "one-two"]=> (s/conform ::temp 12)[:two 12]{:tag :one
:val "one-two"}--
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
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
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+unsubscribe@googlegroups.com.
I think part of the issue is that the article dates back to mid-2015 and `clojure.spec` wasn’t a thing back then, was it?
Variants feel like a solution to a problem for which we have a much better solution _today_ than we did two years ago. The article talks about using core.typed and core.match with variants – because that’s what we had then. I’m fairly sure that if Eric (and Jeanine) were writing their material today, we’d see `clojure.spec` front and center and regular hash maps being used.
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
--
I think part of the issue is that the article dates back to mid-2015 and `clojure.spec` wasn’t a thing back then, was it?
Variants feel like a solution to a problem for which we have a much better solution _today_ than we did two years ago. The article talks about using core.typed and core.match with variants – because that’s what we had then. I’m fairly sure that if Eric (and Jeanine) were writing their material today, we’d see `clojure.spec` front and center and regular hash maps being used.
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
From: Timothy Baldridge
Sent: Tuesday, August 22, 2017 6:00 PM
To: clo...@googlegroups.com
Subject: Re: Sum types in Clojure? Better to represent as tagged records or asvariant vectors?
I think the article is a bit misleading. Variants were never that popular in Clojure. Infact I've never seen them used in production code or the most common Clojure libraries. So I'm a bit perplexed as to why the article recommends them so strongly.
So I think the answer is, they are a fun thought experiment in Clojure, but are of limited usefulness due to the tools we have available that make them unneeded.
It's a bit like recommending that new users use actors in Clojure. Sure, you can shoehorn them in, but there's a reason why they aren't the default.
--
“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 clo...@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
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+unsubscribe@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 clo...@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
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+unsubscribe@googlegroups.com.
Great, so these approaches are suggesting we wrap every value in a vector or a hash map (as the lisp cast article does).
{:event/type :draw :card :4s :deck :player1}[:event/draw :4s :player1]--
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
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscribe@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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
I can see it be quick and concise for representing events, but that's also exactly the use case example for multi-spec: https://clojure.org/guides/spec#_multi_specWhat happens if your event needs more data? Maybe draw needs 2 attributes, the card and the deck? Now you have implicit encoding, where what the attributes are for the event are defined by its position in the vector.
--
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
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscribe@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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Put let's take that and look at it under a engineering lens a bit:>> For example, a series of events that represent a player's moves in a card game:>>>> [:card/draw :9h]
You have an ad-hoc encoding of data here now. Expand it out a bit, and make it machine readable instead of preferring ease of writing and you get:
{:player/action :card/draw
:card/number :9
:card/suit :hearts}
Much easier to introspect, and extensible as future data just flows through without existing code breaking because you add extra data.
>> I've also found it a useful pattern for data access:>>>> [:data/found "Bob"]
I've worked with systems that have done this, and I really dislike it. Because now I have to zip the inputs of a function with the outputs if I want a composable system usable in pipelines and the like.
What's much better:
{:op/status :success
:data/found "Bob"
:data/key "444-434-3323"
:server/ip ....}
Now not only do I know what data I got, but where it came from, the key I originally used, etc.
--
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
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscribe@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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
But Datomic has E in [e a v] which links multiple [a v] pairs into an entity...which is basically a map. So I don't think that applies here.
GET /example HTTP/1.1Host: www.example.com[:request/method :get][:request/uri "/example"][:request/protocol "HTTP/1.1"][:request/header ["host" "www.example.com"]]
Once again, a ad-hoc encoding. What is "GET", what is "/example". I see that datastructure and all I see are hashmaps.Do it the way ring does ;-)
{:method :get
:uri "..."
:headers [...]}
Simple: because failing to put it in a map constrains future growth.
But I guess I'd flip it around. Why would I ever want:
[:response val]
when I could have
{:status :response
:result val}
Currently, I see the big distinction being concise vs extension. Maybe for streaming variants would be better as the format would be smaller to transfer over a wire. And for small short lived programs, or things you know won't need extension, variants offer a slightly more convenient structure to work with.
I think both can be specced easily. S/or a few s/tuple to spec a variant. And multi-spec over a set for the map version.
I'd like to explore then the issue of extensibility with variants. How would you extend them, is there really no way? This is some of my brainstorming thoughts.
1) How to design a variant of more then one value?
1.1)
I know this is a standard variant of one value:
[:image/web "www.myimage.com/image.jpg"]
I believe to extend it to more values, you would do:
[:image/file "/images/image" :jpeg]
That is, you'd threat it as a constructor function, which creates an :image/file type and takes an ordered list of arguments. This way, each variant type can even be overloaded on their arguments the way you'd overload a function.
[:image/file "/images/image"]
Can default to format :png for example, when using the one arg constructor.
1.2)
An alternative is to keep variants as vector pairs, and nest them.
[:image/file [:image/file-path "/images/image"] [:image/file-format:jpeg]]
In this form, variants are more like their map counterpart. Each element is named and itself a variant.
1.3)
You could also decide to limit variants to strict pairs, so the second element of any variant is either a variant or a vector of variants.
[:image/file [[:image/file-path "/images/image"] [:image/file-format:jpeg]]]
Now with both these forms, 1.2 and 1.3, if you threat them again as constructor functions, you now have a form of named parameter on your constructor, allowing mixed order.
1.4)
At this point, the variant has become pretty similar to a map, losing in verbosity over it even. There's just one advantage, the type is not a key/value pair, which I find is more intuitive to use, no need to know the special name of key that holds the type.
1.5)
Let's try to make it concise again.
[:image/file {:image/file-path "/images/image" :image/format :jpeg}]
This hybrid avoids needing a type key, while having named parameters, its the best of both worlds, but it mixes vectors and maps.
1.6)
Here it is with the lispcast suggestion:
{:image/file {:image/file-path "/images/image" :image/format :jpeg}}
What I don't like about this, is how do you group variants together? Do you add more to this map? Do you keep each variant a map of one key and group them on a vector?
It does solve the problem of wanting to pass a vector to your variant though, as the lispcast blog talks about.
1.7)
So I'm left with this form, which Clojure macros and options often use:
[:image/file :image/file-path "/images/image" :image/format :jpeg]
This is harder to spec I think, but you could write a predicate that did, there's just not an existing one that can do it I think.
Now a variant is a tuple with first element being the type, and an alternating pair of key/values. This is extensible like map, yet more concise. It isn't ambiguous to pass in a vector either, and lets you have names while not enforcing order.
Now what if I'd want the option to create my variant with named parameters or not? Some languages allow for powerful constructors like that.
1.8)
To do that, you need a way to distinguish if the rest of the vector is an alternating of named pairs, or an ordered list of arguments. I'm stuck here, I'm not sure its possible without restricting the typed a variant can take. If you group the rest in a vector or a map to indicate named pairs, then you can no longer pass a vector or map argument to a variant, since they'll be interpreted as a named pair list. You could use meta maybe, or a reader tag? Not sure I like those ideas though.
1.conclusion)
I like 1.1 and 1.7 the best.
I find 1.7 might actually be a better alternative to using maps. Its more intuitive, looks like a type constructor, but just like maps it allows arbitrary order and has names for readability while being more concise. Best of both worlds. Its not ambiguous either, you can easily pass in vector arguments.
1.1 is also great, if you don't mind losing named parameters and having implicit ordering. Its also non ambiguous, very concise and allows overloading.
Now, that's when you use them as type constructors. But the "type" you construct from them, after parsing the variant might be best represented as a Clojure map or record. It would be annoying to use a variant like that as an actual datastructure to perform logic on. If you need to get the :image/format value in a lot of places, you probably don't want to be passing around the variant and perform linear search lookup for it, and you can't use any of Clojure's function to modify the variant. You could implement some I guess, like an update-variant. So given this fact, using maps have an advantage that they're more homoiconic, you don't need to parse them, when you construct them the result is not a type constructor, but the actual datastructure you'd want to work with.
2) What can you use them for?
2.1) As pseudo type constructor they work well. For cases where the type is constructed by hand, they're a nice DSL. I find they make sense then for hiccup for example. When your types are constrcuted by the computer, I think maps are better. No need to parse them. It would be cool maybe to deftype an actual variant type. In a way, defrecords are almost that.
2.2) As open sum types. When something expects a variant, it means that thing can be one of any variant type. With namespaced keys, they can be restricted to a smaller open set. So :image/... variants are the set of open image variants. Something can spec that it takes a variant whose namespace is image. Then you're free to extend image variants with more of them, like image/web, image/file, etc.
2.3) As closed sum types. I guess you could also spec something to accept a specific set of specific variants, like either a :success or a :failure variant.
2.4) They could be used as product types too. Just allow the type argument to be a vector.
[[:image/file :image/web] "/images/image" :jpeg "www.myimage.com/image"]
This gets harder to oberload arguments though. Unless you use the named pair version.
[[:image/file :image/web] :image.file/path "/images/image" :image.file/format :jpeg :image.web/url "www.myimage.com/image"]
2.conclusion)
I can see now how Jeanine was saying you can use them as the foundation for types of a programming language. Personally, I'll explore they're use when I'm coming up with DSLs, or any time I need to manually create types, I might use variants to construct them, even if what I'm constructing is a map, they're a little nicer to type and read.
--
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
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
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+unsubscribe@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+u...@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+unsubscribe@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+u...@googlegroups.com.
I've used tagged unions to represent lookup identifiers for variant types and found them to be pleasant to work with. I think part of the reason they don't seem useful in the context of this discussion is that many of the examples given have not actually been variants. For example, in [:image/file "/images/image" :jpeg], every value will have those three data elements, and for {:status :response :result val}, every value will have both of those fields, and their values will be the same type. The identifiers I was working with were in heterogenous collections, so I might have [:image/ref 45] or [:user/name "bob"] or [:project/element "project-slug" 435].