在Go语言中,可以使用两种不同类型的函数参数吗?

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

In Go, can you have function parameters with two distinct types?

问题

你好!以下是你要翻译的内容:

你能在Go中编写一个函数,其参数可以是两种不同的类型吗?例如,如果我编写一个简单的函数,它接受一个int类型的数组/切片,并简单地返回第一个值:

func First(array []int) int {
  return array[0]
}

有没有一种方法可以对其进行类型化,以便我们还可以传入[]string等类型?例如,在TypeScript中,我们可以这样做,而不必将数组类型定义为any

const first = (array: (number | string)[]): number | string => {
  return array[0];
};

我看到有些答案解释了使用interface{}来处理这种情况...也许这是唯一的方法,但它似乎与TS中的any非常接近,感觉可能有更好的方法来实现这个。

英文:

Can you write a function in Go whose parameter can be two different types? For example, if I write a simple function that takes in an array/slice of int and simply returns the first value:

func First(array []int) int {
  return array[0]
}

Is there a way to type this such that we can also pass in a []string, etc.? In TypeScript for example, we can do it like such without having to type the array as any:

const first = (array: (number | string)[]): number | string => {
  return array[0];
};

I've seen answers explaining the use of interface{} for situations like this... and maybe that's the only way, but it seems to close to any in TS and it feels like there might be a better way of doing this.

答案1

得分: 4

(我已经好几年没有使用Go了,早在引入泛型之前,所以我基于Go的泛型文档来回答。)

TypeScript的“泛型”(用引号括起来)与Go的泛型并不是真正可比较的。TypeScript主要是为了能够描述围绕类型擦除(即JavaScript)构建的运行时系统的接口(在抽象意义上),而Go的泛型是... 老实说,我不知道。我不知道Go的泛型是如何实现的,也不知道它们的运行时特性:关于Go泛型的文章甚至是语言作者的博客官方文档网站都没有提到“类型擦除”、“具体化泛型”、“(模板)实例化”或“单态化”等关键术语,所以如果有人对此有更好的理解,请编辑本帖,或在评论中告诉我!

不管怎样,好消息是,从Go 1.18 Beta版本开始,它对泛型的支持包括对泛型约束的支持,还支持将联合类型作为泛型类型参数的约束(尽管我还没有找到关于其他ADT(代数数据类型)如“乘积类型”和“交集类型”的支持的信息)。

(请注意,至少目前为止,Go不支持将联合类型作为具体类型,但实际上这不应该是个问题)


在你的情况下,如果你想要一个函数,它返回一个可能是[]int[]string的切片的第一个元素(如果切片为空,则返回某个默认值),那么你可以这样做:

func First[T int | string](arr []T, ifEmpty T) T {
	for _, v := range arr {
		return v
	}

	return ifEmpty
}

乍一看,你可能会认为这将允许arr切片同时包含intstring值,但这是不允许的(见下文)。请记住,泛型参数是由调用者提供的,并且必须是有效的具体类型,因此First只能被实例化为First[int]First[string],这又意味着arr可以是[]int[]string中的任意一个。

无论如何,下面的完整示例在Go Playground上的Go开发分支上编译和运行:

package main

import "fmt"

func First[T int | string](arr []T, ifEmpty T) T {
	for _, v := range arr {
		return v
	}

	return ifEmpty
}

func main() {

    // First[int]:
	arrayOfInts := []int{2, 3, 5, 7, 11, 13}

	firstInt := First(arrayOfInts, -1)
	fmt.Println(firstInt) // 2

    // First[string]:
	arrayOfStrings := []string{"life", "is short", "and love is always over", "in the morning"}
	
    firstString := First(arrayOfStrings, "empty")
	fmt.Println(firstString) // "life"

}

你还可以将约束T int | string提取出来,将其移到一个接口中,然后将其用作约束。我个人认为这样做更容易阅读,特别是当你可能需要在多个地方重复相同的约束时:

type IntOrString interface {
    int | string
}

func First[T IntOrString](arr []T, ifEmpty T) T {
	for _, v := range arr {
		return v
	}

	return ifEmpty
}

你不能做的事情...

请注意,Go(目前至少如此)不允许将描述联合的type作为变量的类型(也不能将其用作切片的元素类型);你只能将联合用作约束,否则你将会得到“interface contains type constraints”错误。这意味着你不能描述一个既可以包含int值又可以包含string值的数组,并将该接口用作具体数组类型:

package main

import "fmt"

type IntOrString interface {
	int | string
}

func First[T IntOrString](arr []T, ifEmpty T) T {
	for _, v := range arr {
		return v
	}

	return ifEmpty
}

func main() {

	arrayOfIntsOrStrings := []IntOrString{2, "foo", 3, "bar", 5, "baz", 7, 11, 13} // ERROR: interface contains type constraints

	firstValue := First(arrayOfIntsOrStrings, -1)
	fmt.Println(firstValue)
}

./prog.go:19:28: interface contains type constraints
Go build failed.


英文:

(I haven't used Go for a few years, long before they introduced generics - so I'm basing my answer off their documentation for generics)

TypeScript's "generics" (in quotes) aren't really comparable to Go's generics. TypeScript is all about being able to describe an interface (in an abstract sense) for a runtime system built around type-erasure (i.e. JavaScript), while Go's is... <sub>honestly, I have no idea. I just don't know how Go's generics are implemented nor their runtime characteristics: Articles on Go's generics, even from the language authors blog or their own documentation site fail to mention key-terms like type erasure, reified generics, (template) instantiation or monomorph, so if anyone has a better understanding please edit this post, or let me know in a comment!</sub>

Anyway, the good news is that as-of the Go 1.18 Beta, its support for generics includes support for generic constraints, but also support for union types as a constraint for generic type parameters (though I haven't yet found any information regarding support for other ADTs like product types and intersection types).

<sub>(Note that, at least for now, Go won't support union types as concrete types, but in practice that shouldn't be an issue)</sub>


In your case, if you want a function that returns the first element of a slice that could be either []int or []string (or returns some default-value if the slice is empty), then you can do this:

func First[T int | string](arr []T, ifEmpty T) T {
	for _, v := range arr {
		return v
	}

	return ifEmpty
}

While at first-glance you might think that this would allow for the arr slice to simultaneously contain both int and string values, this is not allowed (see below). Remember that generic parameters arguments are supplied by the caller and have to be valid concrete types, so First can only be instantiated as either First[int] or First[string], which in-turn implies that arr can be either []int or []string.

Anyway, the full example below compiles and runs in the Go dev branch on the Go playground:

package main

import &quot;fmt&quot;

func First[T int | string](arr []T, ifEmpty T) T {
	for _, v := range arr {
		return v
	}

	return ifEmpty
}

func main() {

    // First[int]:
	arrayOfInts := []int{2, 3, 5, 7, 11, 13}

	firstInt := First(arrayOfInts, -1)
	fmt.Println(firstInt) // 2

    // First[string]:
	arrayOfStrings := []string{&quot;life&quot;, &quot;is short&quot;, &quot;and love is always over&quot;, &quot;in the morning&quot;}
	
    firstString := First(arrayOfStrings, &quot;empty&quot;)
	fmt.Println(firstString) // &quot;life&quot;

}

You can also extract the constraint T int | string and move it to an interface, and then use that as the constraint, which I personally think is easier to read, especially when you might need to repeat the same constraint in multiple places:

type IntOrString interface {
    int | string
}

func First[T IntOrString](arr []T, ifEmpty T) T {
	for _, v := range arr {
		return v
	}

	return ifEmpty
}

Things you can't do...

Note that Go does not (currently, at least) allow using a type that describes a union as a variable's type by itself (nor can you use as a slice's element type either); you can only use a union as a constraint, otherwise you'll get the "interface contains type constraints" error. Which means you can't describe an array that can contain both int and string values and then use that interface for a concrete array type:

package main

import &quot;fmt&quot;

type IntOrString interface {
	int | string
}

func First[T IntOrString](arr []T, ifEmpty T) T {
	for _, v := range arr {
		return v
	}

	return ifEmpty
}

func main() {

	arrayOfIntsOrStrings := []IntOrString{2, &quot;foo&quot;, 3, &quot;bar&quot;, 5, &quot;baz&quot;, 7, 11, 13} // ERROR: interface contains type constraints

	firstValue := First(arrayOfIntsOrStrings, -1)
	fmt.Println(firstValue)
}

> ./prog.go:19:28: interface contains type constraints
> Go build failed.


huangapple
  • 本文由 发表于 2021年12月31日 07:46:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/70537765.html
匿名

发表评论

匿名网友

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

确定