I'm trying to write a package in Go that computes a equation using a "generic" type. To be specific, I want to implement the runge kutta 5 approximation.
This approximation calculates the value of a (unknown) function y
at the point t0 + h
using only the value of y
at t0
, the start time t0
, the step width h
and a differential equation dgl
which is of the form dy/dt = g(t,y)
where g
is some function.
This approximation behaves exactly the same when working with scalar types as when working with vectors (or even matrices). More generally speaking: It works with everything that can be added/subtracted to a value of the same type and can be scaled by a scalar (for which I use float64
)
So I tried to express this as a Go interface:
type Numeric interface {
Add(rhs Numeric) Numeric
Sub(rhs Numeric) Numeric
Mul(rhs float64) Numeric
}
But when I try to "implement" this interface, I ran into troubles because of the parameters type:
type Vec6F struct {
x, y, z float64
vx, vy, vz float64
}
func (lhs *Vec6F) Add(rhs *Vec6F) rk5.Numeric {
result := new(Vec6F)
result.x = lhs.x + rhs.x
result.y = lhs.y + rhs.y
result.z = lhs.z + rhs.z
result.vx = lhs.vx + rhs.vx
result.vy = lhs.vy + rhs.vy
result.vz = lhs.vz + rhs.vz
return result
}
This gives me the error
cannot use result (type *Vec6F) as type rk5.Numeric in return argument:
*Vec6F does not implement rk5.Numeric (wrong type for Add method
have Add(*Vec6F) rk5.Numeric
want Add(rk5.Numeric) rk5.Numeric
which is, on the one hand absolutely logic to me (because rhs could be another object implementing Numeric)
But on the other hand: How do I express something like that in Go? In C++ I could use operator overloading instead, but thats not possible in go.
In order to be generic your Add
method must take a Numeric
parameter. The normal way to deal with this is with a type assertion like this (on playground)
func (lhs *Vec6F) Add(_rhs Numeric) Numeric {
result := new(Vec6F)
rhs := _rhs.(*Vec6F) // type assertion - will panic if wrong type passes
result.x = lhs.x + rhs.x
result.y = lhs.y + rhs.y
result.z = lhs.z + rhs.z
result.vx = lhs.vx + rhs.vx
result.vy = lhs.vy + rhs.vy
result.vz = lhs.vz + rhs.vz
return result
}
You could also use a type switch if you had different types you wanted to convert between.
Indeed, generics are not supported in go. If you want a type to implement an interface, the methods' prototypes need to match exactly: you would need func (lhs *Vec6F) Add(rhs Numeric) Numeric
.
Here is an attempt to write this method using a type assertion:
func (lhs *Vec6F) Add(rhs Numeric) Numeric {
vrhs := rhs.(*Vec6F)
result := new(Vec6F)
result.x = lhs.x + vrhs.x
result.y = lhs.y + vrhs.y
result.z = lhs.z + vrhs.z
result.vx = lhs.vx + vrhs.vx
result.vy = lhs.vy + vrhs.vy
result.vz = lhs.vz + vrhs.vz
return result
}
It compiles and should work when called with the right types of argument, however, I'd say it's an abuse.
Nothing stops you (except a runtime error) from using this method to add vectors to scalars since they would both implement Numeric
. In the end, you would gain nothing from using the interface abstraction.
The go philosophy would dictate the use of type-specific methods/functions in this case.
There are two problems that you're running into.
1.) The reason it does not compile, and complains about the interfaces not matching is because Vec6F does not satisfy the function signature for rk5.Numeric. Both return value, and input parameters must match type.
http://play.golang.org/p/kc9V9EXxJq fixes that problem but creates a new one...
2.) To make the method signatures match so Vec6F satisfies Numeric's signature it broke the ability to perform numeric operations on the property values. This is because interfaces have only methods, no properties.
In your usecase, would it make sense for the Numeric interface to provide an accessor method which would return a matrix array that the receiver would then perform the Add|Sub|Multi on? This might complicate what needs to be done within each interface implementation's methods but I think would get you what you're looking for.