如何确保自定义数据结构在编译时的安全性

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

How to ensure compile-time safety in custom data structures

问题

我正在编写一些数据结构,以熟悉并学习Go语言,但我在Go语言缺乏泛型方面遇到了困难。

在我的实现中,我选择强制每个用户实现一个接口,以便我的数据结构可以对这些对象进行抽象引用,但我不太喜欢我的解决方案,因为这在编译时无法进行验证,正如你将看到的那样。

Comparer接口

容器中保存的每个对象都必须实现以下签名的Compare函数(如果你只想保存原始类型,这可能会很繁琐):

type Comparer interface {
    Compare(Comparer) int
}

然后,你可以有各种实现该接口的元素,比如float64或自定义结构体:

float64

type number float64

func (n1 number) Compare(comparer Comparer) int {
    n2, _ := comparer.(number)
    if n1 > n2 {
        return 1
    } else if n1 < n2 {
        return -1
    } else {
        return 0
    }
}

Person

type Person struct {
    Age int
}

func (p1 Person) Compare(comparer Comparer) int {
    p2, _ := comparer.(Person)
    if p1.Age > p2.Age {
        return 1
    } else if p1.Age < p2.Age {
        return -1
    } else {
        return 0
    }
}

现在,我可以比较其中的一些对象:

func main() {
    fmt.Println(number(2).Compare(number(4)))    // -1
    fmt.Println(Person{26}.Compare(Person{28}))  // -1
    fmt.Println(Person{26}.Compare(number(28)))  //  1
}

问题在于,我不应该能够比较Personnumber。我意识到我可以在运行时检查类型,但我想找到以下解决方法:a) 在编译时验证类型的方法,或者b) 以不同的方式实现泛型数据结构。

问题

  1. 我知道可以使用内置的数据结构几乎完成所有可能需要的操作... 但是,如果没有泛型或运行时类型检查,有什么方法可以创建自己的数据结构?
  2. 由于Go中的接口实现似乎使用鸭子类型,Go如何在编译时强制执行类型?
英文:

I am writing some data structures to get my feet wet and learn about the Go language and am struggling with Go's lack of generics.

In my implementations I have chosen to force each user to implement an interface so my structures could refer to these objects abstractly but I don't love my solution because this is not verified at compile-time as you will see.

Comparer Interface

Each object that is held in a container must implement a Compare function of the following signature (onerous if all you wanted to hold were raw types)

<!-- language-all: go-lang -->

type Comparer interface {
    Compare(Comparer) int
}

You could then have various elements that implement the interface like float64 or a custom struct:

float64

type number float64

func (n1 number) Compare(comparer Comparer) int {
    n2, _ := comparer.(number)
    if n1 &gt; n2 {
        return 1
    } else if n1 &lt; n2 {
        return -1
    } else {
        return 0
    }
}

Person

type Person struct {
    Age int
}

func (p1 Person) Compare(comparer Comparer) int {
    p2, _ := comparer.(Person)
    if p1.Age &gt; p2.Age {
        return 1
    } else if p1.Age &lt; p2.Age {
        return -1
    } else {
        return 0
    }
}

And now I can compare some of these things:

func main() {
    fmt.Println(number(2).Compare(number(4)))    // -1
    fmt.Println(Person{26}.Compare(Person{28}))  // -1
    fmt.Println(Person{26}.Compare(number(28)))  //  1
}

The problem here is that I should not be able to compare a Person and a number. I realize that I can check the type at runtime but I would like to find either a) a compile-time way to verify the type or b) a different method to implement data structures generically.

Questions

  1. I know that one can do almost everything one might need with the built in data structures ... but how would someone make their own data structures without generics or runtime type checking?
  2. Since interface implementation in Go appears to use duck typing, how does Go enforce types at compile time?

答案1

得分: 2

我的翻译如下:

我的意思是这段代码没有任何不安全的地方...只是没有编译时的安全性。例如,在下面的方法中,第一行对comparer进行了类型断言,如果它不是一个数字,并且你在LHS的第二个项目上没有使用_,那么它将返回一个错误,你可以相应地处理。或者你可以完全不使用它,并且会发生panic,由调用者来处理它(这是合适的,因为调用方法时传入了错误的参数,就像在C#中得到InvalidOperationException一样)。

func (n1 number) Compare(comparer Comparer) int {
    n2, _ := comparer.(number)
    if n1 > n2 {
        return 1
    } else if n1 < n2 {
        return -1
    } else {
        return 0
    }
}

这与像C#这样的语言的区别仅仅在于泛型,它允许你以更多的编译时安全性来执行这些操作(因为你无法调用方法时传入错误的参数)。也就是说,在C#拥有泛型之前,以及在此之前的许多语言中,这些操作并不比你在拥有泛型的语言中经常进行的类型转换更不安全。

英文:

I mean there's nothing unsafe about that code... There just isn't compile time safety. For example, in your method below, the first line does a type assertion on comparer, if it's not a number and you didn't have _ for the second item on the LHS then it would return an error and you could act accordingly. Or you could call it without that at all and a panic will occur leaving it up to the caller to handle it (would be appropriate since they're the person calling the method with wrong arguments, would be like getting an InvalidOperationException in C#).

func (n1 number) Compare(comparer Comparer) int {
    n2, _ := comparer.(number)
    if n1 &gt; n2 {
        return 1
    } else if n1 &lt; n2 {
        return -1
    } else {
        return 0
    }
}

The difference between this and a language like C# is purely in generics, which allow you to do these kinds of things with more compile time safety (because you're not able to call the method incorrectly). That being said, there was a time before C# had generics and many languages before that which didn't feature them at all. These operations are no more unsafe than the casts you do routinely even in languages that do have generics.

huangapple
  • 本文由 发表于 2015年10月14日 04:21:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/33111912.html
匿名

发表评论

匿名网友

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

确定