0

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.

a h
  • 143
  • 1
  • 8
  • `Plus` and `Term` are never referenced. It's not clear to me what you're trying to accomplish. Some code to show how you want this to work would help. – Schwern Mar 05 '18 at 21:01
  • Are you reimplementing [`Rat` from the big package](https://golang.org/pkg/math/big/) as an exercise? – Schwern Mar 05 '18 at 21:03
  • I should have written more information in the original post. I tried to create a minimal example that showcases my problem. The structs themselves could've been anything. With Frac64 I tried to create a minimal example of an immutable struct with an Add function. In the client I'm trying to implement the Num interface for Frac which is a typedef Frac64 that also has an Add function that doesn't match with Num's Add. – a h Mar 05 '18 at 21:08
  • I basically want the people who use my package with the `Term` struct to be able to use any struct they see fit to be used as a coefficient as long as it implements Num. – a h Mar 05 '18 at 21:11
  • An interface is incomplete, IMHO. How does catch a case if a parameter of Add method isn't Frac64? How you convert it? Or return an error? You should add a method to get both parts of a number or converting it for ex. – alexey_p Mar 05 '18 at 21:40

2 Answers2

3

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.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • Thanks for this thorough answer. I have added some additional information in my post. Both of your suggestions change `Frac64`'s `Add` function though, which I cannot do as it is in an external package. – a h Mar 06 '18 at 12:39
  • I came over this though which seems to explain my problem quite clearly: https://stackoverflow.com/a/43507669/2966323 I guess I need to use embedding as you say instead of an alias in order to access the methods – a h Mar 06 '18 at 12:47
0

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
}
Adrian
  • 42,911
  • 6
  • 107
  • 99