It's been implied that there was a serious proposal inside Google that
wasn't made public. See:
https://groups.google.com/d/msg/golang-nuts/-94Fmnz9L6k/4BUxp-JqZFUJ
That post also says why it hasn't been accepted.
Though it might be an oversight, the feature does still appear on the
roadmap, so maybe it's not completely out of the question:
http://golang.org/doc/devel/roadmap.html
- Evan
There have been a few (one is here) over time. I think in general the utility isn't seen to be particularly high, and some of the potential "safety" features (errors when missing a case in a switch) are seen as more hindrance than help.~K
http://tip.goneat.org/doc/go_faq.html#unions
Russ
Interfaces and arithmetic types interact in confusing ways. Go will not be improved by adding arithmetic types.
-rob
The answer is no.
The FAQ need not and should not contain an exhaustive counterargument.
Interfaces and arithmetic types interact in confusing ways.
The simple solution is to say you can't do that (after all, an interface can't contain an interface, so why should a union?).
What does
type RW union {
io.Reader
io.Writer
}
mean? Or is it disallowed?
(That would be pretty unfortunate.)
What happens if you have
var r io.Reader = os.Stdin // an *os.File
var rw RW = r
Can you say rw.(io.Writer) and successfully
pull out the *os.File (which is after all both).
What is the zero value of a union?
Why is that okay?
And so on and so on. It gets complicated very fast.
Russ
We don't have the time to engage in lengthy
design discussions about every language feature
we considered and declined, but this should give
a flavor of the complications.
What does
type RW union {
io.Reader
io.Writer
}
mean? Or is it disallowed?
What happens if you have
var r io.Reader = os.Stdin // an *os.File
var rw RW = r
Can you say rw.(io.Writer) and successfully
pull out the *os.File (which is after all both).
What is the zero value of a union?
Why is that okay?
And so on and so on. It gets complicated very fast.
On Wed, Sep 7, 2011 at 23:40, Steven Blenkinsop <stev...@gmail.com> wrote:
> You should be able to do rw.(*os.File) or rw.(io.ReadWriter) or
> rw.(io.Writer), just assert to whatever type you want, as long as it is a
> type that is assignable to one of the element types of the union. You could
> do rw.(io.Writer).(*os.File) if you want to, but there wouldn't be much
> point.
This is too surprising. It means that if someone
puts in an io.Writer and then does a type switch,
the io.Reader case fires. So the tagged union is
in reality not very tagged. This essentially violates
the definition of a variant type.
>> What is the zero value of a union?
> nil
This is another serious problem. A common reason
someone might want unions is that you want to write
a function that accepts either string or []byte, so you
write
type MyInput union { string; []byte }
func F(MyInput)
and then someone writes F(nil) and gets a nil MyInput
instead of a nil []byte. Now instead of two cases
there are three everywhere. It basically kills this
case as a motivating example.
At this point the weird corner cases are eating away
at the benefits of the hypothetical union types enough
that it's not clear that they're necessary. In the few
cases where you really care, you can write a dummy
interface { IsFoo() }, without any language changes.
Russ
I am going to mute this thread and stop replying after this post.
I just don't have time, sorry.
On Wed, Sep 7, 2011 at 23:40, Steven Blenkinsop <stev...@gmail.com> wrote:This is too surprising. It means that if someone
> You should be able to do rw.(*os.File) or rw.(io.ReadWriter) or
> rw.(io.Writer), just assert to whatever type you want, as long as it is a
> type that is assignable to one of the element types of the union. You could
> do rw.(io.Writer).(*os.File) if you want to, but there wouldn't be much
> point.
puts in an io.Writer and then does a type switch,
the io.Reader case fires. So the tagged union is
in reality not very tagged. This essentially violates
the definition of a variant type.
>> What is the zero value of a union?This is another serious problem. A common reason
> nil
someone might want unions is that you want to write
a function that accepts either string or []byte, so you
write
type MyInput union { string; []byte }
func F(MyInput)
and then someone writes F(nil) and gets a nil MyInput
instead of a nil []byte. Now instead of two cases
there are three everywhere. It basically kills this
case as a motivating example.
At this point the weird corner cases are eating away
at the benefits of the hypothetical union types enough
that it's not clear that they're necessary. In the few
cases where you really care, you can write a dummy
interface { IsFoo() }, without any language changes.
Have you considered making the variant type be an extension of interfaces?
// An interface{} that can only be instantiated from either a Leaf
or Node object.
type LeafNode interface {
*Leaf, *Node
}
This would cause conversions to LeafNode to only be allowed if the
source is either *Leaf or *Node. That gives some guarantee that values
you unpack in a function are of the type you put in originally.
--
Han-Wen Nienhuys
Google Engineering Belo Horizonte
han...@google.com
On Wed, Sep 7, 2011 at 7:58 PM, Rob 'Commander' Pike <r...@google.com> wrote:
> The answer is no. The FAQ need not and should not contain an exhaustive counterargument.Have you considered making the variant type be an extension of interfaces?
>
> Interfaces and arithmetic types interact in confusing ways.
// An interface{} that can only be instantiated from either a Leaf
or Node object.
type LeafNode interface {
*Leaf, *Node
}
This would cause conversions to LeafNode to only be allowed if the
source is either *Leaf or *Node. That gives some guarantee that values
you unpack in a function are of the type you put in originally.
It might be the residual fear of the playground in me, but I'm half
expecting an ASCII art "Russ Cox smells of [something appropriately
smelly that rhymes with grit]" to appear in this thread from you now :).
--
Dr Rich Wareham
Right. There could also be an error if one of the types is missing
from a type switch that uses the interface type,
var ln LeafNode
switch ln.(type) {
case *Leaf:
log.Println("leaf")
} // compile time error: missing case for *Node.
> On Sep 9, 4:25 am, Steven Blenkinsop <steven...@gmail.com> wrote:
>> On Thu, Sep 8, 2011 at 10:39 AM, Han-Wen Nienhuys <han...@google.com> wrote:
>> > On Wed, Sep 7, 2011 at 7:58 PM, Rob 'Commander' Pike <r...@google.com> wrote:
>> > > The answer is no. The FAQ need not and should not contain an exhaustive
>> > counterargument.
>>
>> > > Interfaces and arithmetic types interact in confusing ways.
>>
>> > Have you considered making the variant type be an extension of interfaces?
>>
>> > // An interface{} that can only be instantiated from either a Leaf
>> > or Node object.
>> > type LeafNode interface {
>> > *Leaf, *Node
>> > }
>>
>> > This would cause conversions to LeafNode to only be allowed if the
>> > source is either *Leaf or *Node. That gives some guarantee that values
>> > you unpack in a function are of the type you put in originally.
>>
>> This wouldn't work. Currently, type LeafNode interface { Leaf; Node } means
>> that a LeafNode is the *intersection* of Leaf and Node (which means it has
>> the union of the constraints, i.e. method sets). Making type LeafNode
>> interface { *Leaf; *Node } be the *union* of *Leaf and *Node would be
>> inconsistent behaviour.
>
--
On Fri, Sep 9, 2011 at 1:00 PM, j...@webmaster.ms <j...@webmaster.ms> wrote:Right. There could also be an error if one of the types is missing
> It is only question of the syntax confusion.
> I think, Han-Wen wanted to say that variant types can be equal to
> interface{} with some compile-time constraints.
>
> type LeafNode *Leaf | *Node // let's define it more like they look in
> ML and Haskell in order not to confuse
>
> that would mean
> type LeafNode interface {}
>
> var ln LeafNode
> a := ln.(*Leaf) // ok
> b := ln.(*Node) // ok
> b := ln.(int) // <----------------- error in compile time, not in
> runtime
from a type switch that uses the interface type,
var ln LeafNode
switch ln.(type) {
case *Leaf:
log.Println("leaf")
} // compile time error: missing case for *Node.
Ah. It turned out that I misread the paragraph of text starting with
"This is too ..." written by Russ. My misinterpretation was caused, I
guess, by an inconsistency in the paragraph. I don't understand - how
could a type switch convert an io.Writer into an io.Reader? That's
impossible.
Firstly don't call it a union as that's incorrect: a Variant is a subset of interface{} which can contain any one of the specified types at a given time, not a type which is the union of all the specified types (i.e. let's ditch the C terminology because it confuses matters).
type RW variant {
io.Reader
io.Writer
}
> What happens if you have
>
> var r io.Reader = os.Stdin // an *os.File
> var rw RW = r
>
> Can you say rw.(io.Writer) and successfully pull out the *os.File (which is after all both).
The language spec already specifies the order in which a type switch will compare cases and I cannot see a reason why a type assertion wouldn't return the correct type either.
The more interesting question is whether or not a variant should be allowed to possess its own method set because clearly it cannot be relied upon to expose a consistent method set for its contained items. Personally I think it should as this would allow much of the manual type checking associated with its use to be factored out in one place, however that would make variants and interfaces asymmetric. The spec would also have to explicitly forbid type embedding for variant types, so they'd asymmetric to the normal type system as well.
> What is the zero value of a union?
> Why is that okay?
The obvious answer is nil as that has unique type properties, but that runs counter to the point of a variant type. Perhaps this is the one place in the language where the zero value should be considered explicitly erroneous, so a variant type can be declared without specifying content but any attempt to read from it or invoke methods on it until a value has been assigned will generate a panic.
> And so on and so on. It gets complicated very fast.
Yes, and no. However a robust variant type with its own method set could lead to more succinct code without damaging readability or maintainability so the experiment is certainly worth considering.
Ellie
Eleanor McHugh
Games With Brains
http://feyeleanor.tel
----
if _, ok := reality.(Reasonable); !ok { panic(reality) }
Firstly don't call it a union as that's incorrect: a Variant is a subset of interface{} which can contain any one of the specified types at a given time, not a type which is the union of all the specified types (i.e. let's ditch the C terminology because it confuses matters).
> What is the zero value of a union?The obvious answer is nil as that has unique type properties, but that runs counter to the point of a variant type.
> Why is that okay?
Perhaps this is the one place in the language where the zero value should be considered explicitly erroneous, so a variant type can be declared without specifying content but any attempt to read from it or invoke methods on it until a value has been assigned will generate a panic.
I was actually referring to the code example when I wrote those words, as it looked essentially identical to a union in C. Further there was no suggestion that the union type should also possess its own method set, further supports the implied equivalence - especially to the bulk of programmers who either don't have a background in type theory, or like myself possess only a lazy, hazy recollection of it.
> That brings up the question, what kind of union would actually be useful in Go?
If the purpose of our enquiry is to introduce a union without already having a reason for its existence then certainly this is a good question. However there is a very compelling reason for some kind of union type: to facilitate generic programming.
> You're saying a variant,
In hindsight I'd prefer to call it a category as its three key features:
1. can be described in terms of a singular choice between a set of existing valid types;
2. possesses a method set which expresses homomorphic structure for the types it categorises;
3. doesn't support type embedding so represents a fundamental type;
combined strike me as analogous to a category with formally defined functors. At the same time it's a fully fledged concrete type which can implement interfaces in its own right without ambiguity, and where the contained type can be asserted as required.
This makes it a useful black box for building generic behaviour including solving the underlying problems which caused me to postulate a structural template type some months ago without any of the deficits identified in the subsequent discussion.
> but I'm not sure that would best match Go's type system or type switch syntax.
The internal representation probably wouldn't be much different to that used for interfaces, so similar mechanisms for type assertion etc. should apply with the main weirdness being the introduction of category{} which is equivalent to struct{} - however as the latter is already a valid type I'm not sure this would be a problem in practice.
What syntactic changes can you see the introduction of such a type requiring?
I've proposed a couple of ways around that over the last two years, but unfortunately they've all had (or been perceived to have) defects which were either considered incompatible with Go or didn't mesh with how other languages handle generics.
> > > You're saying a variant,
> >
> > In hindsight I'd prefer to call it a category as its three key features:
> >
> > 1. can be described in terms of a singular choice between a set of existing valid types;
>
> If you're going to do this, you might as well move to explicit tags rather than using types as the tags, which means you end up with variants again (basically).
>
> type Stringer interface { String() string }
> type Cat2 category { A; B }
> type Cat3 category { A; B; C }
> // A and B implement Stringer, C does not
>
> The only way to get a Stringer or a Cat2 from a Cat3 is to fully decompose into the individual types (objects), then assign to some commonly visible variable(s) the result, or repeat the code in each object case. You could create a separate syntax from type assertion for the singular type resolution, but then you'd essentially have two type assertions with slightly different semantics. So you'd have to move away from using the types themselves as the tags. Of course, maybe I'm missing something (see down).
A simple type assertion of the category would be identical to any other type assertion:
c := new(Cat3)
...
...
if c, ok := c.(Stringer); ok {
...
}
...
...
switch c := c.(type) {
case A: // handle type A
case B: // handle type B
case Stringer: // handle type Stringer
}
> > 2. possesses a method set which expresses homomorphic structure for the types it categorises;
However for the behaviours common to the category a simple method call hides the complexity.
> > 3. doesn't support type embedding so represents a fundamental type;
> >
> > combined strike me as analogous to a category with formally defined functors. At the same time it's a fully fledged concrete type which can implement interfaces in its own right without ambiguity, and where the contained type can be asserted as required.
>
> Can you please elaborate on this a bit? I'm not up on category theory (though I'm interested). Explain the way you'd specify it, which is a good test for whether its something even worth thinking about. Math is good for inspiration, but bad for getting programmers to use something. Chances are, I've missed something very fundamentally elegant which negates my concerns.
Disclaimer: I'm not an expert on category theory :)
A category can be considered equivalent to a type, or to a group of types which are isomorphic (i.e. possess similarity of structure) when considered purely in terms of their common functors (homomorphic - or structure-preserving - functions). There's a strong similarity to interfaces in that these identify structural isomorphs based on similarity of method set, however where an interface is inferred based upon the homomorphisms which characterise it, a category would provide a mechanism whereby such homomorphisms could be implemented for a fixed set of types.
Syntactically this would look like this:
type A category {
B
C
}
type Action interface {
DoSomething()
}
func (a A) DoSomething() {
switch a := a.(type) {
case B: a.DoSomethingInB()
case C: a.DoComethingInC()
}
}
func UseCategory(i interface{}) {
if i, ok := i.(Action); ok {
i.DoSomething()
}
if i, ok := i.(A); ok {
i.DoSomething()
}
}
> My spidey sense is telling me this might be a good idea for an experimental functional language...
Probably, but as category theory has roots in topology and structure it's probably applicable to any language.
Ellie
Eleanor McHugh
Games With Brains
http://feyeleanor.tel
----
raise ArgumentError unless @reality.responds_to? :reason
> I am not going to suggest changing Walk, but if you dislike the dual purpose of os.Error it could have returned (skipdir bool, err os.Error). No need for a union.
The signature was complicated enough I already, and I decided that for the very (extremely) rare case of skipping a directory's contents, it was worth not mucking up the signature further.
-rob