简体   繁体   中英

How to write a generic function that accepts any numerical type?

Coming from JS / TS, I wanted to checkout go and make a simple calculator, since there's the difference between int and float, what is the preferred way to write a functions 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?

When generics are added to the language, there will be a better answer

Simplest option now is to just convert arguments at the call site.

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

Go 1.18

With the introduction of type parameters in Go 1.18 ( released in beta ), this will be 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 with the new constraints package that has been added to the standard library:

import "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 ops. 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 (
    "constraints"
    "fmt"
)

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://gotipplay.golang.org/p/5QOb6Rd6Ymi

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 > a) == (b > 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM