a h
a h

Reputation: 143

Interface with methods that return itself

Package term:

type Num interface {
    IsNeg()  bool
    Add(Num) Num
}

type Term struct {
    Coeff Num
    Var   string
}

External package frac64

type Frac64 struct {
    Numer uint64
    Denom uint64
    Neg   bool
}

func (a Frac64) Add(b Frac64) Frac64 { // Pretend this is implemented }

Package client

type Frac Frac64

func (f Frac) IsNeg()  bool   { return f.Neg }
func (f Frac) Add(v Num) Num  { // Call Frac64's Add here? }

How would I go about implementing Add for Frac so that it implements the Num interface?

EDIT: Additional info

The external package frac64 was just an example. I do not intend to use it.

The goal here (and I should have been clearer) is for my package to expose the struct Term that uses Num as one of its two properties. Now, I want users of my package to be able to use big.Rat or Frac64 or int64 or rune or whatever they want, as long as they implement the Num interface.

The problem I have is trying to implement the Num interface for a struct that already has functions with the same name as the functions in Num. This is where Frac64 comes in. I could've also used big.Rat as an example, as it also has a function called Add.

I can't change the implementation of Frac64 (or big.Rat for that matter), and I also can't extend it with an Add function as it already exists. That's why I tried to use type Frac Frac64 (or type Frac big.Rat) and then trying to extend Frac.

I fail to implement Num for Frac though, because I'm not able to call Frac64's Add from the Frac's Add function.

Upvotes: 0

Views: 2391

Answers (2)

Schwern
Schwern

Reputation: 164679

You can solve this with embedding...

type Frac struct {
    *Frac64
}

Now Frac can use Frac64's methods, no need to rewrite them.

// `Frac.New(numer, denom, bool)` would remove this implementation leak.
foo := Frac{
    &Frac64 {
        Numer: 45,
        Denom: 99,
        Neg: false,
    },
}
fmt.Println(foo.IsNeg())

But there's a snag when we try to use Add.

// cannot use foo (type Frac) as type Frac64 in argument to foo.Frac64.Add
fmt.Println(foo.Add(foo))

Embedding only works for inheriting methods. When used as an argument it won't use the embedded reference for you, you'd have to do that explicitly.

The real problem is func (a Frac64) Add(b Frac64) Frac64 does not satisfy the Num interface. If we fix that it works fine because Frac implements Num.

func (a Frac64) Add(b Num) Num {
    return Frac64{
        Numer: 12,
        Denom: 23,
        Neg: false,
    }
}

That works, but it's awkward to build a less specific type, Frac, from a more specific type, Frac64.

From here it becomes a bit more obvious that Frac is a Num with some extensions. Frac should be an interface extending Num and adding a numerator and denominator. Frac64 becomes a type implementing Frac.

type Num interface {
    IsNeg()  bool
    Add(Num) Num
}

type Frac interface {
    Numer() uint
    Denom() uint
    Num
}

type Frac64 struct {
    numer uint64
    denom uint64
    neg bool
}

func (f Frac64) IsNeg() bool {
    return f.neg
}

func (f Frac64) Numer() uint {
    return uint(f.numer)
}

func (f Frac64) Denom() uint {
    return uint(f.denom)
}

func (a Frac64) Add(b Num) Num {
    // Just a placeholder to show it compiles.
    return Frac64{
        numer: 12,
        denom: 34,
        neg: false,
    }
}

This is fine for an exercise. In production, consider using big.Rat.

Upvotes: 3

Adrian
Adrian

Reputation: 46423

You implement it so that it has an identical signature to that of the interface; so it has to be named Add, it has to take a single parameter of type Num, and it has to return a single value of type Num. Note that that doesn't mean it can take or return a value of a type that implements Num - the signatures must be identical:

func (a Frac64) Add(b Num) Num {
    // Pretend this is implemented
    // It can return anything that implements Num
}

Upvotes: 0

Related Questions