3 ms·
I'd like to see sum types (often called product types, union types or enums) introduced. I think they would fit Go well, and get us away from the current awkwar
by atombender 8y ago
I'd like to see sum types (often called product types, union types or enums) introduced. I think they would fit Go well, and get us away from the current awkward "interface hell".
For example, imagine you're implementing an AST. You might have something like:
type Identifier struct {
Name string
}
type StringLiteral struct {
Value string
}
and so on. Currently the only way to unify them is with a dummy interface:
type Expression interface {
isExpression()
}
and then you have:
type (Identifier) isExpression()
type (StringLiteral) isExpression()
// etc.
Then in your compiler you'll have lot of switch statements:
switch e := expr.(type) {
case Identifier:
// ...
case StringLiteral:
// ...
default:
panic("unexpected expression")
}
There are multiple problems here.
One, unifying pure data structs with an interface is not what Go interfaces were intended for, and is pure type system abuse. Fine, so maybe in this case Expression also gets things like GetPos() and what not, which sort of justifies an interface. But there are plenty of use cases where there are no methods.
The other problem is that there's no way to prove at compile-time that a switch is exhaustive. Add another expression struct, and all your switches still compile. Someone made a post-processing tool (go-sumtype), but it's an awkward and buggy hack that has to be run as a "go generate" task next to your development workflow.
Thirdly, performance-wise, this forces every struct to be boxed in an interface.
I'd rather like to be able to declare something like:
type Expression (Identifier || StringLiteral)
It can, I suspect, be simpler than Rust's full-blown sum type support, because you're essentially declaring a new type that holds the possible union of types.
Of course, this could be elegantly extended to errors:
func Open(fn string) (*File || error)
The downside to this, as opposed to something like Rust's Result, is that you can't add methods, so you can't chain these calls without something more.
On the other hand, it would work together with the proposed check/handle mechanism.