【问题标题】:Go (language) general numeric type/interfaceGo(语言)通用数字类型/接口
【发布时间】:2013-01-04 03:18:35
【问题描述】:

我正在尝试在 Go 中编写一个使用“通用”类型计算方程的包。具体来说,我想实现runge kutta 5 逼​​近。

这个近似值仅使用t0处的y的值、开始时间t0、步宽h和一个微分方程dgl,其形式为dy/dt = g(t,y),其中g 是某个函数。

当使用标量类型时,这种近似的行为与使用向量(甚至矩阵)时的行为完全相同。更一般地说:它适用于可以添加/减去相同类型的值并且可以通过标量缩放的所有内容(我使用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 中是不可能的。

【问题讨论】:

  • “off-topic”:求解的微分方程是描述(简单)火星轨道的微分方程。对于 quick'n'dirty 完整代码,请参阅play.golang.org/p/GwehylBbLK

标签: generics interface go


【解决方案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 哲学将规定使用特定于类型的方法/函数。

【讨论】:

  • “使用特定类型的方法/函数”到底是什么意思?能举个例子吗?
  • 就像这样,你不会有一个通用的 Add ,它适用于 Vec6F 和其他类似数字的类型。你会有 addVec6F 等。
【解决方案2】:

您遇到了两个问题。

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

http://play.golang.org/p/kc9V9EXxJq 修复了这个问题,但创建了一个新问题...

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

在您的用例中,Numeric 接口提供一个访问器方法是否有意义,该方法将返回一个矩阵数组,然后接收器将在该数组上执行 Add|Sub|Multi ?这可能会使每个接口实现的方法中需要做的事情变得复杂,但我认为这会让你得到你想要的。

【讨论】:

  • 因为我需要大约 2 小时前的(计算的)结果,所以我将代码更改为使用 float64 切片,这在某种程度上是提供这种访问器方法的激进版本。完整的代码在play.golang.org/p/GwehylBbLK——它可以工作,但在我看来,它远不是好的代码。访问器方法的问题如下:假设我必须使用复数。或者我只想在另一个用例中使用整数值。提供访问器可能变得不可能,因为我不能只对向量/矩阵进行操作,也不能对多项式进行操作。
【解决方案3】:

为了通用,您的Add 方法必须采用Numeric 参数。处理这个问题的正常方法是使用这样的类型断言 (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
}

如果您想在不同的类型之间进行转换,也可以使用类型开关。

【讨论】:

  • Thx,这可能是我要结束的解决方案,尽管我不喜欢类型断言。您转换为指针类型而不是 _rhs.(Vec6F) 以保护不必要的副本,对吗?
  • 它必须是指针类型才能使类型断言起作用。尝试在上面的操场链接上更改它以了解我的意思 - 你会收到错误“不可能的类型断言:Vec6F 没有实现数字(添加方法需要指针接收器)”
猜你喜欢
  • 2021-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多