Golang中math包与泛型的使用探讨
Golang中math包与泛型的使用探讨
为什么 math 包(可能还有其他包)还没有使用泛型进行重写?泛型已经推出好几年了,但我仍然需要在 float64 和 float32/intXX 之间来回转换,例如在使用 math.Abs() 函数时。
感觉 math.Abs() 应该接受所有数值类型,而所有三角函数,如 math.Sin() 等,至少应该接受 float64 和 float32。
这其中有什么我不理解的原因吗?
好的,谢谢,现在我明白为什么它被拒绝了。不过,我仍然认为应该创建一个新的包来支持泛型,例如 math2。由于 math 包仍然存在,向后兼容性将得以保持。
有趣,但为什么它在2022年没有被合并?当Rob Pike说“Patch Set 1: Hold+1”时,这意味着什么?
而且它只修复了 math.Abs() 函数……
func main() {
fmt.Println("hello world")
}
是的,我并不否认这会是件好事,但我不知道在标准库中开创这种先例是否是最佳解决方案。不过,正如那次讨论所言,我也不确定是否存在任何好的解决方案。考虑到在自己的项目中实现变通方法或包装器是如此容易,我不确定在标准库中解决此问题的动力有多大。
是的,在所有编程语言中,尤其是低级语言,我总是最终会拥有自己的一套有用的工具库。实际上,我在生活的各个领域都是如此,我有很多用于木工和房屋维修的夹具和其他自制工具。所以,是的,我想我倾向于那种工具包类型。
考虑到在自己的项目中实现自己的变通方案或封装是多么容易。
而这正是我们观点略有分歧的地方。我不喜欢在自己的项目中为“有缺陷”或“功能有限”的标准库去实现自己的变通方案。我已经厌倦了去修补标准库 🙂
我赞同保持 Go 语言本身简洁的理念,但我不喜欢将标准库也精简到和语言本身一样的程度……不过我想我只能接受现状了……
这是一个很好的问题,它触及了Go泛型引入后标准库更新的核心考量。math包尚未全面采用泛型,主要基于以下几个技术原因:
-
性能与代码生成:
math包中的许多函数(如Sin、Cos、Abs)是编译器内置的,可能直接链接到底层硬件指令。使用泛型会引入一层间接调用,可能影响性能,特别是对于这些高度优化的基础函数。为每个数值类型生成特化的代码是更直接的方式。 -
类型约束的复杂性:虽然可以定义
~float32 | ~float64这样的约束,但math包还涉及int、uint及其变种。对于Abs,整数类型需要返回无符号类型,这会导致复杂的类型推导和可能令人困惑的API。 -
向后兼容性:标准库的修改非常谨慎。直接修改现有函数签名会破坏所有现有代码。更可行的路径是添加新的泛型函数,但这又会导致API膨胀。
-
包级别的泛型函数限制:目前Go不支持包级别的泛型类型参数(如
math[T]),这意味着每个函数都需要单独处理约束,这可能不是最优雅的解决方案。
不过,你可以自己编写泛型包装函数来减少类型转换。例如:
package main
import (
"fmt"
"math"
)
type Number interface {
~float32 | ~float64 | ~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}
func Abs[T Number](x T) T {
switch v := any(x).(type) {
case float32:
return T(math.Abs(float64(v)))
case float64:
return T(math.Abs(v))
case int, int8, int16, int32, int64:
// 对于整数,需要更复杂的处理以避免溢出
// 这里简化处理,实际使用时需注意
if x < 0 {
return -x
}
return x
default:
// 处理无符号整数
return x
}
}
func main() {
fmt.Println(Abs(-3.14)) // float64
fmt.Println(Abs(float32(-2.5))) // float32
fmt.Println(Abs(-42)) // int
fmt.Println(Abs(int64(-100))) // int64
}
对于三角函数,可以创建类似的包装器:
func Sin[T ~float32 | ~float64](x T) T {
switch v := any(x).(type) {
case float32:
return T(math.Sin(float64(v)))
case float64:
return T(math.Sin(v))
default:
panic("unexpected type")
}
}
实际上,Go团队已经在讨论和探索math包的泛型化。在未来的Go版本中,我们可能会看到一个新的包(如math/num)提供泛型的数值操作函数,或者现有函数逐步增加泛型版本。但目前,由于上述原因,特别是性能和兼容性考虑,math包的核心函数仍然保持原样。
社区中已经有一些第三方库提供了泛型数值函数,例如github.com/yourbasic/bit或一些数学库的泛型分支,可以作为临时解决方案。

