1
votes

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.

2
You would need to use reflection for this. It would not be type safe and would be slower.Stephen Weinberg
Possible? yes. Good idea? Almost certainly not, for reasons already mentioned: it will be very slow.Flimzy
Looks like I'll be getting familiar with reflection just so I can run some benchmarks on efficiency. I'll likely be opt-ing out and returning to boilerplate version once I run the benchmarks. Thanks for the feedback.user5055169
Do you need to index into these slices or just append, remove, iterate? If you don't need random access, you might want to consider a different data structure like a linked list (e.g., golang.org/pkg/container/list).Andy Schweig

2 Answers

2
votes

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.)

1
votes

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.