0

I am getting deep into the Go architecture and I have problems with polymorphism. I simplified the problem and created new data for this example to be much more understandable my question.

I have this structure:

type Animal interface {
    speak() string
}

type Cat struct {
    Name string
}

type Dog struct {
    Race string
}

And I want the structs to implement the interface, I proceed like that:

   func (c Cat) speak() string {
    return "Miaw!"
}

func (d Dog) speak() string {
    return "Guau!"
}

func speak(a Animal) string {
    return a.speak()
}

func speaks(a []Animal) string {
    str := ""
    for i := 0; i < len(a); i++ {
        str += a[i].speak()
    }
    return str
}

So what I have created is: The method speak receives an Animal and executes the method speak of the structure given (Animal, which is Cat or Dog), and the method speaks receive an slice of Animal and executes the method speak of the structure given in every index of the slice (Animal, which is Cat or Dog).

And for testing the methods, I implemented this function:

func test()  {
    cat1 := Cat{
        Name: "Cat1",
    }

    cat2 := Cat{
        Name: "Cat2",
    }

    cat3 := Cat{
        Name: "Cat3",
    }

    arrayCats := []Cat{cat1, cat2, cat3}
    speak(cat1)
    speak(cat3)
    speak(cat2)
    speaks(arrayCats) //This line gives an error to the Compiler
}

I upload the error that the compiler gives to me:

Error_compiler

Can someone please explain to me why I can play with polimorfish in a functions that receives only one element and why not in the function that receives an slice of that element?

I really need to find the solution to this problem to implement it in different parts of my application, and I have no idea how to solve the problem nor how to implement a practical and scalable solution (the slice in the real application will contain a hight number of elements).

I found this answers related useful to understand more my problem, but still I don't get what is the problem or the solution: Answer1 Answer2Answer3

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
kike
  • 658
  • 1
  • 8
  • 26
  • Don't include screen shots of text. Copy and paste the text instead. If you must include an image, include it directly, rather than as a link. – Jonathan Hall Jan 26 '19 at 12:47
  • Your `speak` requires a `[]Animal` but you try to call it with a `[]Cat`. This is wrong. It is like trying to call `speak` with a `[]map[float64]uint16`. The two types `[]Cat` and `[]Animal` have nothing in common. The fact that Cat implements Animal has **no** **importance** whatsoever on the relation between the types `[]Cat` and `[]Animal`. Stop trying to do inheritance based OOP in Go now and forever unless you want to hurt yourself: It **does** **not** **work**. – Volker Jan 26 '19 at 13:11

2 Answers2

2

Go can’t type cast arrays. You need to create new array of target type manually, then use for loop to type cast each element of source array and put result to target array.

Reason: go doesn’t hide from you memory allocations, this is reason why you need to create new and feel it manually.

https://golang.org/doc/faq#convert_slice_of_interface

Alex Sharov
  • 154
  • 1
  • 9
  • I am worried about the performance of that solution, it has to make a lof of conversions for every element in the array. From a point of view of clean and readable code, I can use this solution, but I think that making a specific method for every element (array of cats and array of dogs) will be more performance, because we don't need to make this conversions for every element, but it will be a lot of redundant code, because we have the same method duplicated for different types, this is the limitation of the language? Or go for readability or go for performance? There is no combined solution? – kike Jan 26 '19 at 10:59
  • Yes, this is limitation of language, but in real life it’s very rare problem (you implementing example from OOP book, but in real life such code I saw very rare). Instead of “worry about performance”, just measure it - run benchmark with -benchmem and -benchcpu flags, you will see which lines are actually slow and which are fast. – Alex Sharov Jan 26 '19 at 11:12
  • 2
    @kike, "I am worried about the performance of that solution" That is precisely why Go doesn't do it for you. It doesn't want to hide [a potentially expensive conversion](https://golang.org/doc/faq#convert_slice_of_interface) from the programmer. Having to write the loop makes the cost clear as day. – Peter Jan 26 '19 at 13:10
1

A fixed version, on Play

As others have mentioned, there is no automatic type coercion in Go. And []Cat is totally different than []Animal.

All you really needed to do was create a slice of the type you will pass to the speaks method.
So, change:

arrayCats := []Cat{cat1, cat2, cat3}

to

arrayCats := []Animal{cat1, cat2, cat3}

And it works fine.

If you are used to languages like Java, they go about this in a really different way in that java treats all things as an Object. So, when they added generics it was a simple compile time trick where the collection was really just a list of Object and the compiler inserts a cast at the extraction point.

Things like int are not objects in java which is why you can't create a List<int>, because then the List<Object> casting trick wouldn't work. Because int isn't a subclass of Object.

In go, there's no common base type for things like struct, so there's no real way to do the cast trick that java does.

As others have mentioned, there are also some specific features on how go manages memory that makes things like auto-conversion not work in a generic way (ie: a slice of int64 is a different memory footprint than a slice of int32, so not castable without allocation).

the go team has stated they are looking in to adding generics in some form, which would potentially make things like this easier to write. But that will come with a cost of invisible runtime or compile time complexity (or both).

Today, since it is not automatic, you have to write the code to do the conversion. Which has the downside of you write more code. And the upside of it being obvious what's happening in the system.

David Budworth
  • 11,248
  • 1
  • 36
  • 45