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