使用空接口强制执行“通用”代码类型

时间:2012-08-10 19:30:04

标签: go

对于模棱两可的标题感到抱歉。

我正在阅读本书http://algs4.cs.princeton.edu/home/,我认为将Go中的示例作为一种学习练习来实现是好的,但本书使用Java作为其语言来描述代码。

第一章的其中一章讨论了设置一些核心数据类型/容器样式类以便以后重用,但是我在尝试将这些数据转换为Go设置时遇到了麻烦,主要是因为这些数据类型似乎正在享受使用Java泛型。

例如,我编写了以下代码

package bag

type T interface{}
type Bag []T

func (a *Bag) Add(t T) {
    *a = append(*a, t)
}

func (a *Bag) IsEmpty() bool {
    return len(*a) == 0
}

func (a *Bag) Size() int {
    return len(*a)
}

原则上我可以将项目添加到Bag并检查其大小和所有内容。但是,这也意味着以下代码是合法的

a := make(bag.Bag,0,0)
a.Add(1)
a.Add("Hello world!")
a.Add(5.6)
a.Add(time.Now())

我只是想知道是否有任何强制执行类型的方式,因此它符合类似于

的合同
Bag<T> bagOfMyType = new Bag<T>()

e.g。

Bag<Integer> bagOfInts = new Bag<Integer>()

我知道Go没有泛型,他们不是真正的The Go Way,但我只是想知道是否有可能在编译时“强制执行”任何事情(可能不是)

对不起,很长的帖子

编辑:好的,所以我一直在深入研究这个问题,我几乎放弃了仿制药的一面(我明白这不是Go的路线图)所以我正在考虑做一些类似于带接口的Haskell类型类,比如

type T interface{}
type Bag interface {
    Add(t T)
    IsEmpty() bool
    Size() int
}

type IntSlice []int

func (i *IntSlice) Add(t T) {
    *i = append(*i, t.(int)) // will throw runtime exception if user attempts to add anything other than int
}

func (i *IntSlice) IsEmpty() bool {
    return len(*i) == 0
}

func (i *IntSlice) Size() int {
    return len(*i)
}

此问题是类型强制仅在运行时强制执行。

任何人都有任何想法如何改善这个?

2 个答案:

答案 0 :(得分:5)

我是Go的新手,所以我很好奇是否有人会有更好的答案,但这就是我的看法:

您希望编译时强制执行,在Add()上调用IntSlice时,其参数为int。好吧,这就是你如何做到的:

func (i *IntSlice) Add(t int) {
    *i = append(*i, t)
}

由于没有泛型,Add()方法对于每种类型的Bag都会有所不同,因此Bag接口(假设您需要它)变为:< / p>

type Bag interface {
    IsEmpty() bool
    Size() int
}

这对我来说很有意义,因为你不能传递Bag并在其中扔任何东西。知道某些内容是Bag并不足以知道如何在其上调用Add();你必须知道你正在处理的Bag是什么类型。

你可以使接口特定于类型,如IntBag,但由于只有一种类型实际上会满足该接口,你也可以摆脱接口并更改{{1的名称转到IntSlice

基本上这意味着完全放弃任何类似通用的东西,只是创建一个类型,其中包含一些你想要的方法:

IntBag

更新:有时候仿制药真的会派上用场。在我看来,我们仍然会根据具体情况选择对于特定问题最恰当的选择。使用空接口和反射,您可以获得一些类似通用的行为,但它往往是丑陋的,您放弃了一些编译时类型检查。或者你放弃了泛型并且有一些代码重复。或者你只是采用完全不同的方式。

几周前我问了一个关于我应该如何使用Go处理我认为需要类层次结构的问题的问题。答案基本上是没有通用的解决方案;这完全是个案。我认为同样适用于泛型:Go中没有泛型,并且没有通用的解决方案将基于泛型的解决方案转换为Go。

在很多情况下,您可能会在另一种语言中使用泛型,但在Go中界面完全足够(或真正闪耀)。您的示例是接口不是真正替代的示例。另见:Go Vs. Generics

答案 1 :(得分:1)

我非常精通Go。泛型是一个备受争议的话题,目前没有类似于Java泛型或C ++模板的东西。目前的惯例是实施一个&#34;泛型&#34;使用空接口键入,然后使用特定类型实现将其包装,以确保仅使用该类型的元素。以下是Go标准库中container/list的示例。

http://play.golang.org/p/9w9H1EPHKR

package main

import (
    "container/list"
    "fmt"
)

type IntList struct {
    innerList *list.List
}

func NewIntList() *IntList {
    return &IntList{list.New()}
}

func (l *IntList) Add(i int) {
    // this is the only way to add an element to the list,
    // and the Add() method only takes ints, so only ints
    // can be added
    l.innerList.PushBack(i)
}

func (l *IntList) Last() int {
    lastElem := l.innerList.Back()

    // We can safely type-assert to an int, because Add()
    // guarantees that we can't put a non-int into our list
    return lastElem.Value.(int)
}

func main() {
    l := NewIntList()
    l.Add(5)
    l.Add(4)
    l.Add(3)
    l.Add(2)
    l.Add(1)
    fmt.Println("Expecting 1; got:", l.Last())
}