你可以使用泛型函数来接受任何数值类型。

huangapple go评论92阅读模式
英文:

How can I write a generic function that accepts any numerical type?

问题

从JavaScript和TypeScript转到Go,我想尝试一下,并制作一个简单的计算器。由于int和float之间存在差异,编写一个接受任意数字的函数的首选方式是什么?

例如:

package main

func add(a float64, b float64) float64 {
  return a + b;
}

func main() {
  a := 1;
  b := 2;
  fmt.Println(add(1, 2)); // 3
  fmt.Println(add(a, b)); // Cannot use a (type int) as type float64 in argument to add
  fmt.Println(add(1.5, 3.2)); // 4.7
  fmt.Println(add(2.5, 2)); // 4.5
}

我需要将所有内容转换为float(因为它“覆盖”了int范围),还是为每种类型创建单独的函数,如addInt(a int, b int) intaddFloat(a float64, b float64) float64?或者可能有更优雅的方式吗?

英文:

Coming from JavaScript and TypeScript, I wanted to check out Go and make a simple calculator. Since there's the difference between int and float, what is the preferred way to write a function that takes any number?

For example:

package main

func add(a float64, b float64) float64 {
  return a + b;
}

func main() {
  a := 1;
  b := 2;
  fmt.Println(add(1, 2)); // 3
  fmt.Println(add(a, b)); // Cannot use a (type int) as type float64 in argument to add
  fmt.Println(add(1.5, 3.2)); // 4.7
  fmt.Println(add(2.5, 2)); // 4.5
}

Do I need to convert everything to float (since it "covers" the int range) or do I create a separate functions for each type, like addInt(a int, b int) int and addFloat(a float64, b float64) float64? Or might there be a more elegant way at all?

答案1

得分: 23

Go 1.18及以上版本

随着Go 1.18引入了类型参数,这变得更容易实现。

您可以定义一个以T为参数的函数,并使用接口约束T限制为数值类型。

func add[T Number](a, b T) T {
	return a + b
}

约束Number可以使用golang.org/x/exp/constraints(仍处于实验阶段)进行定义:

import "golang.org/x/exp/constraints"

type Number interface {
    constraints.Integer | constraints.Float
}

其中:

  • Numberconstraints.Integerconstraints.Float类型集合的并集
  • constraints.Integer是所有有符号和无符号整数类型的集合
  • constraints.Float是浮点数类型的集合

这将允许您使用任何两个数值类型的参数调用add函数。然后,在函数体中,您将能够使用约束中所有类型都支持的任何操作。因此,在处理数字时,这也包括算术运算符。然后,声明类似的函数很容易:

func multiply[T Number](a, b T) T {
	return a * b
}

请记住,参数必须具有相同的类型。无论是否使用泛型,您都不能使用不同的类型;根据规范Operators

> [...] 除移位或无类型常量外,操作数类型必须相同。

因此,我们的泛型addmultiply函数只定义了一个类型参数T。这意味着您也不能使用默认类型不兼容的无类型常量调用add函数:

add(2.5, 2) // 无法编译通过

在这种情况下,编译器将从第一个参数2.5推断出T的类型,默认为float64,然后无法匹配默认为int2的类型。

完整程序:

package main

import (
	"fmt"

	"golang.org/x/exp/constraints"
)

type Number interface {
	constraints.Integer | constraints.Float
}

func main() {
	a := 1
	b := 2
	
	fmt.Println(add(1, 2))     // 3
	fmt.Println(add(a, b))     // 3
	fmt.Println(add(1.5, 3.2)) // 4.7
	// fmt.Println(add(2.5, 2)) // 默认类型int的2与推断类型float64的T不匹配
}

func add[T Number](a, b T) T {
	return a + b
}

Playground: https://go.dev/play/p/rdqi3_-EdHp

**警告:**由于这些函数还处理浮点数,要记住浮点数可以包含NaN值和无穷大。


关于复数

Go有预声明的complex64complex128类型。您也可以在Number约束中使用它们:

import "golang.org/x/exp/constraints"

type Number interface {
    constraints.Integer | constraints.Float | constraints.Complex
}

这不会限制这些泛型函数的功能:整数和浮点数支持的算术运算符(仅限+-*/)以及所有比较运算符也同样适用于复数类型。取余运算符%和位运算符仅支持整数,因此仅适用于受限于constraints.Integer的类型参数。

英文:

Go 1.18 and above

With the introduction of type parameters in Go 1.18, this is easier to accomplish.

You can define a function parametrized in T and use an interface constraint to restrict T to numeric types.

func add[T Number](a, b T) T {
	return a + b
}

The constraint Number can be defined using golang.org/x/exp/constraints package (still experimental):

import "golang.org/x/exp/constraints"

type Number interface {
    constraints.Integer | constraints.Float
}

Where:

  • Number is the union of the type sets of constraints.Integer and constraints.Float
  • constraints.Integer is the set of all signed and unsigned integer types
  • contraints.Float is the set of float types

This will allow you to call add with any two arguments of numeric type. Then in the function body you will be able to use any operation that is supported by all types in the constraint. So in case of numbers, this includes also arithmetic operators. Then declaring similar functions is easy:

func multiply[T Number](a, b T) T {
	return a * b
}

Keep in mind that the arguments must have the same type. Regardless of generics, you can't use different types; from the specs Operators:

> [...] the operand types must be identical unless the operation involves shifts or untyped constants.

Therefore our generic add and multiply functions are defined with only one type parameter T. This implies that you also can't call the add function with untyped constants whose default types are incompatible:

add(2.5, 2) // won't compile

In this case the compiler will infer the type of T from the first argument 2.5, which defaults to float64, and then won't be able to match the type of 2, which defaults to int.

Full program:

package main

import (
	"fmt"

	"golang.org/x/exp/constraints"
)

type Number interface {
	constraints.Integer | constraints.Float
}

func main() {
	a := 1
	b := 2
	
	fmt.Println(add(1, 2))     // 3
	fmt.Println(add(a, b))     // 3
	fmt.Println(add(1.5, 3.2)) // 4.7
	// fmt.Println(add(2.5, 2)) // default type int of 2 does not match inferred type float64 for T
}

func add[T Number](a, b T) T {
	return a + b
}

Playground: https://go.dev/play/p/rdqi3_-EdHp

Warning: since these functions handle also floats, keep in mind that floats can hold NaN values and infinities.

<hr>

About complex numbers

Go has complex64 and complex128 predeclared types. You can use them too in the Number constraint:

import &quot;golang.org/x/exp/constraints&quot;

type Number interface {
    constraints.Integer | constraints.Float | constraints.Complex
}

This doesn't restrict the capabilities of these generic functions: the arithmetic operators that are supported by integers and floats (only +, -, * and /) and all order operators are supported by complex types too. The remainder operator % and bitwise operators are supported only by integers, and therefore by type parameters constrained to constraints.Integer.

答案2

得分: 3

直到Go 1.17版本(泛型之前),最简单的选择是在调用处将参数进行转换。

add(float64(a), float64(b))
英文:

Up until Go 1.17 (pre-generics). See other answer(s) for an updated solution


The simplest option is to just convert arguments at the call site.

add(float64(a), float64(b))

答案3

得分: 1

现在由于泛型的存在,这是可能的,但是非常繁琐,因为你必须在函数声明中手动指定每种数值类型。

// Adds two integers and returns the result together with a boolean
// which indicates whether an overflow has occurred
func AddInt[I int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](a, b I) (I, bool) {
    c := a + b
    if (c > a) == (b > 0) {
        return c, true
    }
    return c, false
}

你还可以定义一个包含冗长的整数类型列表的接口。

type Int interface {
    int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64
}

// Adds two integers and returns the result together with a boolean
// which indicates whether an overflow has occurred
func AddInt[I Int](a, b I) (I, bool) {
    c := a + b
    if (c > a) == (b > 0) {
        return c, true
    }
    return c, false
}

还有一个名为 constraints 的包提供了一种更加DRY(Don't Repeat Yourself)的定义此类函数的方式,但它是实验性的,并且可能在某个时候从语言中移除,所以我不建议使用它。

英文:

It is possible now thanks to generics, but it's extremely tedious because you have to specify every numeric type by hand in the function declaration.

// Adds two integers and returns the result together with a boolean
// which indicates whether an overflow has occurred
func AddInt[I int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](a, b I) (I, bool) {
    c := a + b
    if (c &gt; a) == (b &gt; 0) {
	    return c, true
    }
    return c, false
}

You could also define an interface that will contain the verbose list of int types

type Int interface {
	int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64
}

// Adds two integers and returns the result together with a boolean
// which indicates whether an overflow has occurred
func AddInt[I Int](a, b I) (I, bool) {
	c := a + b
	if (c &gt; a) == (b &gt; 0) {
			return c, true
	}
	return c, false
}

There is a constraints package that provides a more DRY way of defining such functions, but it's experimental and could be removed from the language at some point, so I wouldn't recommend using it.

huangapple
  • 本文由 发表于 2021年5月25日 04:05:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/67678331.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定