Generics 如何在同一函数中接受不同类型的切片?

Generics 如何在同一函数中接受不同类型的切片?,generics,go,slice,Generics,Go,Slice,我有一个函数removeFrom,它从切片中删除一个项目。它接受一个float64切片和一个索引: func removeFrom(slice []float64, index int) []float64 { if len(slice) > index { return append(slice[:index], slice[index+1:]...) } } 它工作得很好,但现在我还必须从整数切片中删除。那么,我如何更改它以接受这两种类型(并返回给定类

我有一个函数
removeFrom
,它从切片中删除一个项目。它接受一个
float64
切片和一个索引:

func removeFrom(slice []float64, index int) []float64 {
    if len(slice) > index {
        return append(slice[:index], slice[index+1:]...)
    }
}

它工作得很好,但现在我还必须从整数切片中删除。那么,我如何更改它以接受这两种类型(并返回给定类型的一个片段)?我尝试使用空接口,但显然我必须在函数内部进行一些转换,但我没有找到如何进行转换。

简短回答?你不能

回答很长,你仍然不能直接做,但是:

func removeFrom(slice interface{}, index int) interface{} {
    switch slice := slice.(type) {
    case []float64:
        if len(slice) > index {
            return append(slice[:index], slice[index+1:]...)
        }
    case []int64:
        if len(slice) > index {
            return append(slice[:index], slice[index+1:]...)
        }
    case []int:
        if len(slice) > index {
            return append(slice[:index], slice[index+1:]...)
        }
    default:
        log.Panicf("unknown type: %T", slice)
    }
}

Go不支持泛型,所有切片类型都没有“公共祖先”(例如,
[]接口{}
[]int
不“兼容”,请参阅以获取更多详细信息)

因此,如果希望函数接受任何切片类型,则必须使用
接口{}
(用于“传入”参数和返回类型)。但是现在您有了一个(接口)包装器值,您不能对其应用切片,也不能将其传递给内置函数

您可以对已知类型使用and,但必须对每个类型重复代码,因此这并不是真正的领先

实际上,有一种方法可以创建一个
removeFrom()
函数,该函数将使用反射来处理所有切片类型

是描述任何Go值的类型。它支持不同类型的Go值,包括切片

我们感兴趣的是方法:

我们可以用它来切一片。好。这是我们的元素去除算法中的一个关键点。仍然需要“连接”两个切片,一个在可移动元素之前,一个在可移动元素之后。幸运的是,
reflect
包也支持这一点:

作为最后一个剩余的关键点,我们可以使用来获取任何切片的长度

我们现在已经具备了常规
removeFrom()
函数所需的一切,它非常简单:

func removeFrom(s interface{}, idx int) interface{} {
    if v := reflect.ValueOf(s); v.Len() > idx {
        return reflect.AppendSlice(v.Slice(0, idx), v.Slice(idx+1, v.Len())).Interface()
    }
    return s
}
真的,就这些。测试它:

for i := 0; i < 4; i++ {
    fmt.Println(removeFrom([]int{0, 1, 2}, i), "missing:", i)
}
for i := 0; i < 4; i++ {
    fmt.Println(removeFrom([]string{"zero", "one", "two"}, i), "missing:", i)
}
注意事项:


此解决方案使用反射,因此它将比另一个不使用反射但具有具体的、受支持的类型“连接”的解决方案慢。快速基准测试表明,这种通用解决方案比有线输入类型的非反射慢2.5倍。应权衡性能或便利性/通用解决方案是否更重要。或者您可以将其与具体类型相结合:您可以添加一个类型开关来处理频繁的类型,并且只有在实际的具体类型不由类型开关处理时,才返回到这个通用解决方案。

Wow。。好的,我认为最好对每种类型有一个单独的方法:P谢谢!那么
case[]float64,[]int64,[]int:doSmt
呢?我试图运行上面的代码,但是len的
参数片(类型interface{})无效,…@u\u mulder它不会工作,因为这样片的类型将是
interface{}
。这就是我要找的。谢谢请注意,与类型断言相比,它的速度要慢得可怕。@这个通用解决方案中有一个使用反射,所以是的,它将比“连接”具体类型的东西慢。“可怕”是一个强有力的词,因为我认为两者之间的区别是数量级。快速基准测试显示,一般的反射解决方案的速度慢了2.5倍。我无法编辑它,但很抱歉,这是一个很强的词。
func removeFrom(s interface{}, idx int) interface{} {
    if v := reflect.ValueOf(s); v.Len() > idx {
        return reflect.AppendSlice(v.Slice(0, idx), v.Slice(idx+1, v.Len())).Interface()
    }
    return s
}
for i := 0; i < 4; i++ {
    fmt.Println(removeFrom([]int{0, 1, 2}, i), "missing:", i)
}
for i := 0; i < 4; i++ {
    fmt.Println(removeFrom([]string{"zero", "one", "two"}, i), "missing:", i)
}
[1 2] missing: 0
[0 2] missing: 1
[0 1] missing: 2
[0 1 2] missing: 3
[one two] missing: 0
[zero two] missing: 1
[zero one] missing: 2
[zero one two] missing: 3