5

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.

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • "off-topic": the differential equation solved is the one describing the (simple) mars orbit. For the quick'n'dirty complete code see http://play.golang.org/p/GwehylBbLK – Daniel Jour Jan 21 '13 at 14:03

3 Answers3

2

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.

Nick Craig-Wood
  • 52,955
  • 12
  • 126
  • 132
  • Thx, this is probably the solution i'm going to end with, although I don't like type assertions. You convert to a pointer type instead of `_rhs.(Vec6F)` to safe a unneccessary copy, right? – Daniel Jour Jan 21 '13 at 14:02
  • It had to be a pointer type to make the type assertion work. Try changing it on the playground link above to see what I mean - you'll get the error "impossible type assertion: Vec6F does not implement Numeric (Add method requires pointer receiver)" – Nick Craig-Wood Jan 21 '13 at 19:58
1

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.

lbonn
  • 2,499
  • 22
  • 32
0

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.

Jason D.
  • 513
  • 4
  • 9
  • 1
    Because I needed the results (of the computation) about 2 hours ago, I had changed the code to use slices of float64 instead, which is somehow a radical version of providing such a accessor method. The complete code is at http://play.golang.org/p/GwehylBbLK -- It works, but it is far away from being nice code, in my opinion. The problem with the accessor method is the following: Suppose I had to use complex numbers. Or I would only want Integer values in another use case. Providing an accessor then becomes probably impossible, as I couldnt only operate on vectors/matrices but also on polynoms. – Daniel Jour Jan 21 '13 at 13:58