通用的切片删除函数

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

Generic remove() function for slices

问题

我有3个切片(foos,bars,bazs),每个切片都填充有不同类型的结构体。为了减少一些样板代码,我想创建一个通用的remove(slice, struct) slice函数,类似于标准库中提供的**append()**的相反操作。

这些结构体都不会是指针,所以不需要将它们置为nil。我尝试使用**interface{}**来实现期望的结果,但没有成功。当前的实现使用了类型切换(Type Switch),然后有一个几乎完全复制粘贴的remove()函数(在下面的playground链接中有示例),用于从切片中删除元素。随着项目的扩展,这种样板代码会越来越多。

尝试的示例:
https://play.golang.org/p/9UPRIIp5M2

函数输入:[]slices,struct
期望输出:
如果找到了struct,则修改后的(删除了struct的)[]slices
如果没有找到struct,则保持不变的[]slices

如果这个实现简单而容易,我想它应该已经存在于标准库中。然而,向经验丰富的专业人士寻求建议,询问我所尝试的是否可行,从来没有坏处。

谢谢您的时间。

英文:

I have 3 slices (foos, bars, bazs) that are each populated with a different type of struct. In an attempt to remove some boilerplate code, I wanted to create a generic remove(slice, struct) slice function. Similar to being the opposite of append() provided in the standard.

None of the structs will be pointers so there's no need to nil them. I've flirted with the idea of using interface{} to get the desired result to no avail. Current implementation uses a Type Switch and then has a near copy-pasted remove() (example in playground link below) to delete from the slice. As I continue to expand the project- it will grow to more boilerplate.

Example of what is being attempted:
https://play.golang.org/p/9UPRIIp5M2

Function input: []slices, struct
Expected output: 
    Modified (removed struct) []slices if struct is found
    Or, Unmodified []slices if it isn't.

If it was simple and easy to implement. I imagined it would already exist in the standard. However, it never hurts to get the advice from more seasoned professionals as to if what I am attempting to do is even possible.

Thank you for you time.

答案1

得分: 3

尝试使Go语言具有泛型是新手Go开发人员的一个巨大陷阱。停止吧。你正在节省五行代码:

for i := len(foos) - 1; i >= 0; i-- {
    if foos[i] == foo1 {
        foos = append(foos[:i], foos[i+1:]...)
    }
}

是的,在泛型语言中,你可以将这五行代码封装成一个漂亮的标准库方法,但Go不是一种泛型语言。尝试使用反射来实现这一点会很慢,但这不是避免使用它的原因。反射非常复杂。很难弄对。你将花费比重写这五行代码12次(包括修复你意外剪切/粘贴错误的时间和你弄错i--的一次)更多的时间来理解Value并追踪奇怪的边界情况。只需直接编写它们。

直接编写代码可以让你决定什么是相等的。它让你决定是否在找到第一个匹配项后停止搜索,还是继续遍历整个列表。它让你根据程序的实际需求来做事,而不是关注某个泛型程序可能在将来需要的东西。

我喜欢泛型编程。很少有什么比在Haskell中创建一个优雅的fold更让我开心。但这不是Go的方式。在Go中,通常只需编写代码,保持简单和明显,然后继续前进。

Andy提到了一个很好的观点,如果你经常这样做,list可能是一种更好的数据结构。我经常发现,当我有三种类型似乎都有相似方法时,它们实际上应该是一个单一的结构的一部分(你真的需要在这里分开的列表吗?)但无论如何,除非你有一个非常专门的问题,你真的需要“任何东西”而不是“这些少量的东西”之一,否则要远离反射。

(你提到了append()很值得注意。我认为在Go中不可能编写append()。这就是为什么它必须成为语言的一部分而不是标准库函数的原因。当我开始使用Go时,我认为这是语言的一个重大缺陷。我在Go中工作的时间越长,我发现它并不那么重要。你只需编写代码并继续前进。)

英文:

Trying to make Go generic is one of the great pitfalls for new Go devs. Stop. You're saving five of lines of code:

for i := len(foos) - 1; i >= 0; i-- {
	if foos[i] == foo1 {
		foos = append(foos[:i], foos[i+1:]...)
	}
}

Yes, in generic languages, you would wrap those five lines up in to a nice stdlib method, but Go is not a generic language. Trying to do this with reflection is slow, but that's not the reason to avoid it. Reflection is very complicated. It's hard to get it right. You'll spend much more time figuring out Value and chasing weird corner cases than you'll spend rewriting those five lines of code 12 times (including fixing the time you accidentally cut/paste it wrong, and the one time you mess up i--). Just write them.

Just writing the code lets you decide what equality means. It lets you decide whether to stop searching at the first match, or keep going through the whole list. It lets you do what this program needs rather than focusing on what some generic program might someday need.

I love generic programming. Few things make me happier than creating an elegant fold in Haskell. But that's not the way of Go. In Go you generally just write the code, keep it simple and obvious, and move on.

Andy makes a good point that if you have to do this a lot, list may be a better data structure. And I often find that when I have three types that all seem to have parallel methods, it turns out that they should all have been part of a single struct (do you really need separate lists here at all?) But in any case, stay away from reflection unless you have a very specialized problem where you really mean "anything" rather than "one of these short list of things."

(It's noteworthy that you call out append(). I don't think it's possible to write append() in Go. That's why it had to be part of the language rather than a stdlib function. When I started working in Go, I took that as a significant flaw in the language. The longer I've worked in Go, the more I've found it not to matter so much. You just write the code and move on.)

答案2

得分: 1

我看了你的示例代码和其中的错误。要了解更多关于interface slice的内容,可以参考这个Go wikiSliceTricks


通用实现有点复杂,你可以选择基于类型的实现。然而,基于类型的实现会带来更多的样板代码/重复代码。

你需要使用reflect包来实现你的目的,正如@stephen-weinberg在评论中提到的。

所以最好的起点是尝试使用这个库github.com/anzhihun/generic,并阅读库的代码,然后自己实现。这个库使用了反射。

英文:

I had a look on your sample code and its errors. To learn more about interface slice is to refer this Go wiki and SliceTricks.


Generic implementation is bit complex instead you can go with type based implementation. However Type based implementation brings more code on boilerplate/repetitive.

You need to use reflect package for your purpose as @stephen-weinberg mentioned in the comment.

So best starting point would be; try this library github.com/anzhihun/generic and go through library codebase, implement on your own. This library uses reflection.

huangapple
  • 本文由 发表于 2017年7月14日 02:15:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/45088229.html
匿名

发表评论

匿名网友

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

确定