Golang中math包与泛型的使用探讨

Golang中math包与泛型的使用探讨 为什么 math 包(可能还有其他包)还没有使用泛型进行重写?泛型已经推出好几年了,但我仍然需要在 float64float32/intXX 之间来回转换,例如在使用 math.Abs() 函数时。

感觉 math.Abs() 应该接受所有数值类型,而所有三角函数,如 math.Sin() 等,至少应该接受 float64 和 float32。

这其中有什么我不理解的原因吗?

8 回复

请参考其他讨论的链接。

更多关于Golang中math包与泛型的使用探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


好的,谢谢,现在我明白为什么它被拒绝了。不过,我仍然认为应该创建一个新的包来支持泛型,例如 math2。由于 math 包仍然存在,向后兼容性将得以保持。

有趣,但为什么它在2022年没有被合并?当Rob Pike说“Patch Set 1: Hold+1”时,这意味着什么?

而且它只修复了 math.Abs() 函数……

func main() {
    fmt.Println("hello world")
}

是的,我并不否认这会是件好事,但我不知道在标准库中开创这种先例是否是最佳解决方案。不过,正如那次讨论所言,我也不确定是否存在任何好的解决方案。考虑到在自己的项目中实现变通方法或包装器是如此容易,我不确定在标准库中解决此问题的动力有多大。

是的,在所有编程语言中,尤其是低级语言,我总是最终会拥有自己的一套有用的工具库。实际上,我在生活的各个领域都是如此,我有很多用于木工和房屋维修的夹具和其他自制工具。所以,是的,我想我倾向于那种工具包类型。slight_smile

考虑到在自己的项目中实现自己的变通方案或封装是多么容易。

而这正是我们观点略有分歧的地方。我不喜欢在自己的项目中为“有缺陷”或“功能有限”的标准库去实现自己的变通方案。我已经厌倦了去修补标准库 🙂

我赞同保持 Go 语言本身简洁的理念,但我不喜欢将标准库也精简到和语言本身一样的程度……不过我想我只能接受现状了……

这是一个很好的问题,它触及了Go泛型引入后标准库更新的核心考量。math包尚未全面采用泛型,主要基于以下几个技术原因:

  1. 性能与代码生成math包中的许多函数(如SinCosAbs)是编译器内置的,可能直接链接到底层硬件指令。使用泛型会引入一层间接调用,可能影响性能,特别是对于这些高度优化的基础函数。为每个数值类型生成特化的代码是更直接的方式。

  2. 类型约束的复杂性:虽然可以定义~float32 | ~float64这样的约束,但math包还涉及intuint及其变种。对于Abs,整数类型需要返回无符号类型,这会导致复杂的类型推导和可能令人困惑的API。

  3. 向后兼容性:标准库的修改非常谨慎。直接修改现有函数签名会破坏所有现有代码。更可行的路径是添加新的泛型函数,但这又会导致API膨胀。

  4. 包级别的泛型函数限制:目前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或一些数学库的泛型分支,可以作为临时解决方案。

回到顶部