Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go (language) general numeric type/interface

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.

like image 894
Daniel Jour Avatar asked Oct 22 '22 19:10

Daniel Jour


2 Answers

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.

like image 111
Nick Craig-Wood Avatar answered Oct 27 '22 10:10

Nick Craig-Wood


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.

like image 26
lbonn Avatar answered Oct 27 '22 09:10

lbonn