|
It depends on what you mean exactly by "emulating sum types" and "pattern matching them". An example is rolling your own discriminated union: type Avariant struct {
a int
}
type Bvariant struct {
b string
}
type SumChoice int
const (
SumA SumChoice = 0
SumB SumChoice = 1
)
type Sum struct {
type SumChoice
A *Avariant
B *Bvariant
}
sumA := Sum{choice: SumA, A: &{a: 7} }
sumB := Sum{choice: SumB, B: &{b: "abc"} }
func foo (x Sum) {
switch(x.choice) {
case SumA: {
fmt.Printf("Value is %d", x.A.a)
}
case SumB: {
fmt.Printf("Value is \"%s\"", x.B.b)
}
}
}
As usual with Go, it's ugly and verbose and error prone, but it just about works.The previous poster was probably thinking of something similar but using interface{} (equivalent to Object or void*) instead of Sum, and then pattern matching using an explicit type switch: func foo (x interface{}) {
switch actual := x.(type) {
case AVariant: {
fmt.Printf("Value is %d", actual.a)
}
case BVariant: {
fmt.Printf("Value is \"%s\"", actual.b)
}
}
}
This is slightly less risky and has less ceremony, but it's also less clear which types foo() supports based on its signature. Since Go has only structural subtyping [0] for interfaces, you would have to add more boilerplate to use a marker interface instead.[0] In Go, a type implements an interface if it has functions that match (name and signature) the interface functions. The name of the interface is actually irrelevant: any two interfaces with the same functions are equal. So if you declare an interface MyInterface with no methods, it is perfectly equivalent to the built-in `interface{}`: any type implements this interface, and any function which takes a MyInterface value can be called with any type in the program. |
On the first approach, with "error prone" you mean the tag could be incorrect, right? (or even have an impossible variant with 0 or multiple variants set).