Go(语言)通用数字类型/接口

5
我正在尝试用Go编写一个包,使用“通用”类型计算方程。具体来说,我想实现龙格库塔五阶逼近法。
这种逼近法仅使用未知函数y在t0时刻的值、起始时间t0、步长h和形式为dy/dt = g(t,y)的微分方程dgl来计算点t0 + h处y的值。
这种逼近法在使用标量类型和向量(甚至矩阵)时表现完全相同。更一般地说:它适用于任何可以与相同类型的值相加/相减并且可以通过标量(我使用float64)进行缩放的东西。
因此,我尝试将其表示为Go接口:
type Numeric interface {
    Add(rhs Numeric) Numeric
    Sub(rhs Numeric) Numeric
    Mul(rhs float64) Numeric
}

但是当我尝试“实现”这个接口时,由于参数类型的原因,我遇到了麻烦:
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
}

这导致我出现错误。
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

一方面,对我来说这是绝对合乎逻辑的(因为rhs可以是实现Numeric接口的另一个对象)

但另一方面:我该如何在Go中表达这样的内容呢?在C++中,我可以使用运算符重载,但在Go中不可能。


“离题”: 解决的微分方程是描述(简单)火星轨道的方程。要查看快速且完整的代码,请访问http://play.golang.org/p/GwehylBbLK - Daniel Jour
3个回答

2
为了使你的Add方法具有通用性,它必须接受一个Numeric参数。处理此问题的正常方式是使用类型断言,例如这样(在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
}

如果您需要转换不同类型的内容,可以使用类型转换(type switch)。


谢谢,这可能是我最终采用的解决方案,尽管我不喜欢类型断言。你将其转换为指针类型而不是 _rhs.(Vec6F) 来避免不必要的复制,对吗? - Daniel Jour
它必须是一个指针类型才能使类型断言起作用。尝试在上面的游乐场链接中进行更改,以了解我的意思 - 您将收到错误消息“不可能的类型断言:Vec6F未实现Numeric(Add方法需要指针接收器)”。 - Nick Craig-Wood

1

事实上,Go语言不支持泛型。

如果你想让一个类型实现一个接口,方法的原型需要完全匹配:你需要使用 func (lhs *Vec6F) Add(rhs Numeric) Numeric

这里是一种尝试使用类型断言编写此方法的方式:

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
}

它可以编译并在使用正确类型的参数时正常工作,但我认为这是一种滥用。

除了运行时错误之外,没有任何东西阻止您使用此方法将向量添加到标量,因为它们都实现了Numeric。最终,使用接口抽象不会带来任何好处。

在这种情况下,Go哲学将指导使用特定于类型的方法/函数。


2
“使用特定类型的方法/函数”是什么意思?你能举个例子吗? - Daniel Jour
就像您不会拥有适用于Vec6F和其他数字类型的通用Add函数一样。您需要编写addVec6F等函数来完成特定操作。 - Edward Z. Yang

0

你遇到了两个问题。

1.) 它无法编译并抱怨接口不匹配的原因是Vec6F不满足rk5.Numeric的函数签名。返回值和输入参数都必须匹配类型。

http://play.golang.org/p/kc9V9EXxJq解决了这个问题,但又引入了一个新问题...

2.) 为了使方法签名匹配,使Vec6F满足Numeric的签名,它破坏了对属性值执行数值操作的能力。这是因为接口只有方法,没有属性。

在您的用例中,Numeric接口是否提供了一个访问器方法,该方法将返回一个矩阵数组,接收器将在其上执行Add|Sub|Multi操作?这可能会使每个接口实现方法内部需要完成的工作变得复杂,但我认为这可以得到您想要的结果。


1
因为我大约两个小时前就需要计算结果了,所以我已经更改了代码,使用float64的切片来进行计算,这是一种提供访问器方法的激进版本。完整的代码在http://play.golang.org/p/GwehylBbLK --它可以工作,但在我看来,它离优美的代码还有很长的距离。访问器方法的问题在于:假设我必须使用复数。或者在另一个用例中,我只想要整数值。那么提供访问器可能变得非常困难,因为我不仅要操作向量/矩阵,还要操作多项式。 - Daniel Jour

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接