Reputation: 143
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?
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
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
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