I am writing some data structures to get my feet wet and learn about the Go language and am struggling with Go's lack of generics.
In my implementations I have chosen to force each user to implement an interface so my structures could refer to these objects abstractly but I don't love my solution because this is not verified at compile-time as you will see.
Each object that is held in a container must implement a Compare
function of the following signature (onerous if all you wanted to hold were raw types)
type Comparer interface {
Compare(Comparer) int
}
You could then have various elements that implement the interface like float64
or a custom struct:
type number float64
func (n1 number) Compare(comparer Comparer) int {
n2, _ := comparer.(number)
if n1 > n2 {
return 1
} else if n1 < n2 {
return -1
} else {
return 0
}
}
type Person struct {
Age int
}
func (p1 Person) Compare(comparer Comparer) int {
p2, _ := comparer.(Person)
if p1.Age > p2.Age {
return 1
} else if p1.Age < p2.Age {
return -1
} else {
return 0
}
}
And now I can compare some of these things:
func main() {
fmt.Println(number(2).Compare(number(4))) // -1
fmt.Println(Person{26}.Compare(Person{28})) // -1
fmt.Println(Person{26}.Compare(number(28))) // 1
}
The problem here is that I should not be able to compare a Person
and a number
. I realize that I can check the type at runtime but I would like to find either a) a compile-time way to verify the type or b) a different method to implement data structures generically.
Questions
I mean there's nothing unsafe about that code... There just isn't compile time safety. For example, in your method below, the first line does a type assertion on comparer
, if it's not a number and you didn't have _
for the second item on the LHS then it would return an error and you could act accordingly. Or you could call it without that at all and a panic
will occur leaving it up to the caller to handle it (would be appropriate since they're the person calling the method with wrong arguments, would be like getting an InvalidOperationException
in C#).
func (n1 number) Compare(comparer Comparer) int {
n2, _ := comparer.(number)
if n1 > n2 {
return 1
} else if n1 < n2 {
return -1
} else {
return 0
}
}
The difference between this and a language like C# is purely in generics, which allow you to do these kinds of things with more compile time safety (because you're not able to call the method incorrectly). That being said, there was a time before C# had generics and many languages before that which didn't feature them at all. These operations are no more unsafe than the casts you do routinely even in languages that do have generics.