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].
It would certainly be just as easy to use {:collection/type ::person :user/name "bob"} and {:collection/type ::project :project/slug "project-slug" :project/element 435}, but I like the concision of the tagged unions. On Friday, August 25, 2017 at 4:30:37 PM UTC-4, tbc++ wrote: > > >> they're a little nicer to type and read > > And that's where I have to disagree. The problem with most of these > options is they complect ordering with meaning. > > [:image/file "/images/image" :jpeg] > > Here I have no idea what these values mean. I have to have out-of-band > information about what offset in the vector corresponds to what value. > Functions have this same problem, look no further than `cons` vs `conj` to > see potential confusion on argument ordering. > > So why don't we only use maps for function arguments? Well mostly to make > the functions easier to manipulate by humans. But some of the best > libraries for web APIs (AWS) that I've used have used almost an exclusively > map based interface. > > Once again I have to repeat my mantra about DSLs. Don't start with DSLs. > Start with maps. Build everything off of maps. They're extensible, easy to > introspect, and can drive a host of metaprogramming algorithms. If maps are > to hard to understand, build constructor functions on top of them. Then > finally build a DSL on top, if you need it. > > Frankly, I have so many things I have to remember during programming. I'd > much rather see a very uniform map-based interface. Than any sort of nested > vectors, tagged values, or anything else. > > Surely we can't say that this: > >> [[:image/file :image/web] :image.file/path "/images/image" : > image.file/format :jpeg :image.web/url "www.myimage.com/image"] > > Is a better interface than: > > {:image.file/path "/images/image" > :image.file/format :jpeg > :image.web/url "www.myimage.com/image"} > > And as I said before, spec is designed from the start to support data in > this format. Stating "this is a file", "this is a web image". Is just book > keeping that doesn't need to be done. Is a map a :image/web? Well check its > members and see if they match the spec. If they match, then you have a > :image/web. No need for sum types, tags, wrapping values in vectors. Simply > state what a thing should have for it to conform to an interface, and > forget whatever else might be in there. It couldn't be simpler. > > On Fri, Aug 25, 2017 at 11:59 AM, Didier <did...@gmail.com <javascript:>> > wrote: > >> Thanks for everyone's great input. >> >> 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 >> <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.