zian
zian

Reputation: 553

function to return an Interface

How come I can say that the result of CreateLion(), a pointer to a struct that implements the Cat interface, is an instance of the Cat interface, and yet I cannot say that CreateLion() is of type "function that returns the Cat interface."

What is the standard Golang approach to achieving this type of behavior?

package main

import "fmt"

func main() {
    var lion Cat := CreateLion()
    lion.Meow()

    // this line breaks. Why?
    var cf CatFactory = CreateLion
}

type Cat interface {
    Meow()
}

type Lion struct {}
func (l Lion) Meow() {
    fmt.Println("Roar")
}

// define a functor that returns a Cat interface
type CatFactory func() Cat

// define a function that returns a pointer to a Lion struct
func CreateLion() *Lion {
    return &Lion{}
}

Upvotes: 35

Views: 71369

Answers (6)

psychob
psychob

Reputation: 83

The reason lies in the way that Go type system has been designed.

Using Liskov substitution principle, we can replace an expression of type A (expression which when evaluated will result in a value of type A) with an expression of type B provided that B is a subtype of A. We show this relation as B < A.

However, B < A does not automatically mean that type func() B < type func() A (although it makes sense).

The language designers have to decide how the language type constructors (func, slice, map, ...) handle the subtype relations. In case of Go, they decided to keep the func type constructor invariant with respect to its return type (and also its parameters).

Now, in your case you can address this by defining this subtype relationship yourself:

func AsCatFactory[Sub Cat](fn func() Sub) CatFactory {
    return func() Cat { return fn() }
}
// if you are on older Go versions without generics
func AsCatFactory(fn func() *Lion) CatFactory {
    return func() Cat { return fn() }
}

And then use it in your code like this:

    var cf CatFactory = AsCatFactory(CreateLion)

In this way, you have explicitly told the compiler that this substitution is safe. And you have done that because *Lion is a more specific return type than Cat which is in line with the following rule (see):

It is safe to substitute a function f for a function g if f accepts a more general type of argument and returns a more specific type than g

Contrary to what other answers have said, a sound software engineering principle is that your return types should always be as specific as possible (aka Postel's Law). Therefore, func CreateLion() *Lion is completely correct.

Upvotes: 2

Aleksandr
Aleksandr

Reputation: 126

Go doesn't need any special logic for the task you request, it will implicitly convert *Lion to interface Cat

Example:

package main

import "fmt"

type Cat interface {
    Meow()
}

type Lion struct{}

func (l Lion) Meow() {
    fmt.Println("Roar")
}

func CreateLion() *Lion {
    return &Lion{}
}

func main() {
    var lion *Lion = CreateLion()
    lion.Meow()

    var cf Cat = CreateLion()
    cf.Meow()
}

Try it here: https://go.dev/play/p/PFp3fnXP8YH

Upvotes: 2

Atif
Atif

Reputation: 1547

Works fine with a few changes. Check it out here: https://play.golang.org/p/ECSpoOIuzEx

package main

import "fmt"

func main() {
    lion := CreateLion() // Go idomatic style recommends 
                         // allowing the compiler to divine the type
    lion.Meow()

    CatFactory := CreateLion
    _ = CatFactory // Go doesn't like unused variables and fails the build

    obj := CatFactory()     // exercising our factory method
    obj.Meow()
}

type Cat interface {
    Meow()
}

type Lion struct {}
func (l Lion) Meow() {
    fmt.Println("Roar")
}

// define a functor that returns a Cat interface
type CatFactory func() Cat

// define a function that returns a pointer to a Lion struct
func CreateLion() *Lion {
    return &Lion{}
}

Also, though Go doesn't have Java style interfaces, it does have interfaces and you can achieve polymorphism, but the types are known at compile time.

You can model an "Is A" relationship, if both types implement the same interface. However it doesn't enforce the interface until you pass the object into a function that accepts that interface type. So if you imagine implementing the Strategy pattern, when you're passing in the strategy object matching interface "Cat", that function will accept a "Lion" object, or any other class that implements a Meow function with the correct signature.

Also, factory methods are definitely necessary and useful in Go. In fact, instead of constructors, in Go, you use factory functions to construct your objects.

Upvotes: -1

Qudaci
Qudaci

Reputation: 51

The problem here is that statically typed go differentiates the "this is a function that returns a cat" from "this is a function that returns a lion that is a cat" And therefore will not accept one as the other.

The way to fix this is to give your factory var exactly what it expects:

var cf CatFactory = func() Cat{
    return CreateLion()
}
catlion := cf()
catlion.Meow()

Upvotes: 5

Oleksiy Chechel
Oleksiy Chechel

Reputation: 1384

Try this:

package main

import "fmt"

type Cat interface {
    Meow()
}

type Lion struct{}

func (l Lion) Meow() {
    fmt.Println("Roar")
}

type CatFactory func() Cat

func CreateLion() Cat {
    return Lion{}
}

func main() {
    lion := CreateLion()
    lion.Meow()

    var cf CatFactory = CreateLion
    fLion := cf()
    fLion.Meow()
}

In most cases, you can assign any type to base type interface{}. But situation changes if type of function parameter is a map[T]interface{}, []interface{} or func() interface{}. In this case the type must be the same.

Upvotes: 24

joy miao
joy miao

Reputation: 309

I think you should read this blog http://blog.golang.org/laws-of-reflection,it is precise about the relation between variables,types and interfaces.

In your example *Lion is different with Cat.

You can correct function CreateLion returns from *Lion to Cat.

Upvotes: 6

Related Questions