Go type definition operation "inheritance"?

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

Go type definition operation "inheritance"?

问题

Go语言规范中对类型定义的描述如下:

类型定义创建了一个具有相同底层类型和操作的新类型,并将一个标识符绑定到它上面。这个新类型被称为定义类型。它与任何其他类型都不同,包括它所创建的类型。

对于这个描述,我有两个问题:

  1. "与给定类型相同的操作"是什么意思,"操作"的范围是什么(即什么算作操作)?比如我定义了type A int[]type B map[string]int,"相同操作"是否意味着我可以在类型为A的变量上使用索引操作,在类型为B的变量上使用与键相关的操作?

  2. 我不太理解这个描述,为什么新类型与其底层类型不同,但保留了操作?所以,唯一的区别是它们有不同的方法吗?

英文:

The Go language specification describes type definition as follows:

>A type definition creates a new, distinct type with the same underlying type and operations as the given type, and binds an identifier to it. The new type is called a defined type. It is different from any other type, including the type it is created from.

I have two questions about this description:

  1. What does "operation as the given type" mean, and what is the scope of "operation"(i.e. what counts as operation)? Say I define type A int[] and type B map[string]int, does "same operation" means I can use indexing on variables of type A and key-related operations on variables of type B?

  2. I don't quite understand this description, why is the new type different with its underlying type while keeping the operations? So, the only difference is that they have different methods?

答案1

得分: 2

查看“作为给定类型的操作”的含义:

“类型定义创建一个具有与给定类型相同操作的新的独立类型。”

是的,这意味着如果你可以在原始类型上使用索引运算符,你也可以在新类型上使用索引运算符。如果你可以在原始类型上应用+加法运算符,你也可以在新类型上应用它。如果你可以在原始类型上应用<-接收运算符(例如双向通道),你也可以在新类型上应用等等。

基本上,你对一个值所做的任何操作都是一个操作。对它应用运算符,将它传递给函数,调用它的方法。一个操作是否被允许/有效是由值的类型决定的,而方法调用是否有效取决于值的类型的方法集合是否包含给定的方法。

新类型是不同的,因为类型定义创建了一个新的命名类型,根据规范:类型标识

两个类型要么是_相同的_,要么是_不同的_。

定义的类型总是与任何其他类型不同。

新类型在定义上是不同的。新类型将没有从原始类型“继承”的任何方法,这在你不希望新类型实现某些接口时非常方便,有关详细信息和示例,请参见https://stackoverflow.com/questions/66168418/inheritance-syntax-what-is-the-difference/66168456#66168456

当然,你可以向新类型添加新的方法。向现有类型添加方法可能是_不希望的_,或者可能是_不可能的_(例如,因为旧类型可能是内置类型或者可能在你无法控制的包中定义,而方法只能在定义包中添加)。

类型标识(不同)在可赋值性中也起到了作用。例如,如果未命名类型的值可以赋给命名类型的变量,如果底层类型匹配,但命名类型的值不能赋给另一个命名类型的变量,即使底层类型匹配(没有显式转换)。

例如:

type Foo []int
type Bar Foo

var i []int = []int{1}
var f Foo = i // OK,[]int 是未命名类型
var b Bar = f // 不OK,Foo 是命名类型

请注意,“允许的操作”在即将发布的Go版本中将具有更大的意义,因为泛型将添加到下一个(1.18)版本中,你可以使用类型参数的约束,并且可以应用于某些类型的操作将限制可以用作这些类型参数的类型参数的类型。例如,如果我们有一个简单的泛型add()函数:

func add[T constraints.Ordered | constraints.Complex](a, b T) T {
	return a + b
}

我们可以使用intfloat64complex64等来调用它。但是如果我们有自己定义的类型:

type MyInt int

由于MyInt的值可以应用于与int相同的操作,我们也可以将MyInt用作上述T类型参数的类型参数:

fmt.Println(add(1, 2))       // add[int]
fmt.Println(add(1+2i, 3+4i)) // add[complex64]
// 是的,下面也可以工作,它将进行字符串连接
fmt.Println(add("1", "2"))           // add[string]
fmt.Println(add(MyInt(1), MyInt(2))) // add[MyInt]

输出将是(在Go Playground上尝试一下):

3
(4+6i)
12
3
英文:

Look at the meaning of "operation as the given type" with context:

"A type definition creates a new, distinct type with the same operation as the given type."

And yes, this means if you could use the index operator on the original type, you can also index the new type. If you could apply the + addition operator on the original type, you can also apply it on the new type. If you could apply the &lt;- receive operator on the original type (e.g. a bidirectional channel), you can also apply on the new type etc.

Basically everything you (may) do with a value is an operation. Applying operators on it, passing it to a function, calling a method on it. Whether an operation is allowed / valid is determined by the value's type, and whether a method call is valid depends on if the method set of the value's type contains the given method.

The new type is different because the type definition creates a new, named type, and Spec: Type identity:

> Two types are either identical or different.
>
> A defined type is always different from any other type.

The new type is different by definition. The new type will have zero methods "inherited" from the original type, which comes handy when you don't want the new type implementing certain interfaces, for details and examples, see https://stackoverflow.com/questions/66168418/inheritance-syntax-what-is-the-difference/66168456#66168456

You may of course add new methods to your new type. It may be unwanted to add methods to the existing type, or it may be impossible (e.g. because the old type may be a builtin type or may be defined in a package not under your control, and methods can only be added in the defining package).

Type identity (being different) also plays a role in assignability. E.g. a value of unnamed type can be assigned to a variable of named type if the underlying types match, but a value of named type cannot be assigned to a variable of another named type even if the underlying types match (without explicit conversion).

For example:

type Foo []int
type Bar Foo

var i []int = []int{1}
var f Foo = i // OK, []int is unnamed type
var b Bar = f // Not OK, Foo is a named type

Note that the "operations allowed" will have greater significance in the upcoming Go versions, as generics is added to the next (1.18) release, where you use constraints for type parameters, and what types may be used as type arguments for those type parameters is restricted by the operations that can be applied on certain types. For example if we have a simple generic add() function:

func add[T constraints.Ordered | constraints.Complex](a, b T) T {
	return a + b
}

We may call it with int, float64, complex64 for example. But if we have our own defined type:

type MyInt int

Since the same operations can be applied on values of MyInt than that of int, we can also use MyInt as a type argument for the above T type parameter:

fmt.Println(add(1, 2))       // add[int]
fmt.Println(add(1+2i, 3+4i)) // add[complex64]
// Yes, below works too, it will be string concatenation
fmt.Println(add(&quot;1&quot;, &quot;2&quot;))           // add[string]
fmt.Println(add(MyInt(1), MyInt(2))) // add[MyInt]

Output will be (try it on the Go Playground):

3
(4+6i)
12
3

答案2

得分: 0

新类型保留了底层类型的操作。但由于它是一个不同的、新的类型,你可以为其添加更多的操作。例如,我为类型A添加了一个"addall"方法。

package main

import "fmt"

type A []int
type B map[string]int

func (a A) alladd(increment int) A {
    for i := 0; i < len(a); i++ {
        a[i] = a[i] + increment
    }
    fmt.Println(a[0])
    return a
}

func main() {
    var x A
    x = append(x, 122)
    x[0] = x[0] + 1
    var y B = make(B)
    y["ABC"] = 999
    x = x.alladd(27)
    fmt.Println(x, y)
}

链接:https://go.dev/play/p/Od1_-SXk_uO

英文:

The new type keeps the operations from the underlying type. But as it is a different, new type you can then add more operations to it. For example, I've added an "addall" method for type A

package main

import &quot;fmt&quot;

type A []int
type B map[string]int

func (a A) alladd(increment int) A {
	for i := 0; i &lt; len(a); i++ {
		a[i] = a[i] + increment
	}
	fmt.Println(a[0])
	return a
}

func main() {
	var x A
	x = append(x, 122)
	x[0] = x[0] + 1
	var y B = make(B)
	y[&quot;ABC&quot;] = 999
	x = x.alladd(27)
	fmt.Println(x, y)
}

https://go.dev/play/p/Od1_-SXk_uO

huangapple
  • 本文由 发表于 2022年1月6日 19:53:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/70606715.html
匿名

发表评论

匿名网友

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

确定