Let's say I have the following pipeline of functions:
func func3(opts ...FunctionObject) {
for _, opt := range opts {
opt()
}
}
func func2(opts ...FunctionObject) {
var functions []FunctionObject
functions = append(functions, someFunction3)
functions = append(functions, someFunction4)
...
...
...
func3(append(functions, opts...)...)
}
func func1(opts ...FunctionObject) {
var functions []FunctionObject
functions = append(functions, someFunction)
functions = append(functions, someFunction2)
...
...
...
func2(append(functions, opts...)...)
}
For reasons inherited in the problem I want to solve, the functions in functions should be called before the functions in opts , so i can't just append to opts but I have to prepend functions to opts (by append(functions, opts...) ) and then using ... again to send it to next function in the pipeline, so im getting the weird expression:
func2(append(functions, opts...)...)
I don't know how efficient it is, but Im sure it looks weird,
There must be a better way of doing it, and that's what Im looking for.
yet i'd be grateful for accompanying explanation about efficiency :)
Edit:
I can't change the the argument type from opts ...FunctionObject to opts []FunctionObject (as @dev.bmax suggested in comments) since im making changes in an existing codebase so i can't change the functions that call func{1,2,3}
- by saying that "it looks weird" i don't mean only of the "look" but it looks weird to do this operation (ellipsis) twice, and it seems to be inefficient (am i wrong?)
Prepending to a slice is fundamentally inefficient since it will require some combination of:
It would be more efficient if you could change the calling convention between functions to only append options and then process them in reverse. This could avoid repeatedly moving items to the end of the slice and potentially all allocations beyond the first (if enough space is allocated in advance).
Note:
func3(opts ...FunctionObject) / func3(opts...)andfunc3(opts []FunctionObject) / func3(opts)are equivalent for performance. The former is effectively syntactic sugar for passing the slice.However, you've mentioned you need to keep your calling conventions...
Your example code will cause allocations for the 1st, 2nd, 3rd, 5th,.. append within each function - allocations are needed to double the size of the backing array (for small slices).
append(functions, opts...)will likely also allocate if the earlier appends didn't create enough spare capacity.A helper function could make the code more readable. It could also reuse spare capacity in the
optsbacking array:Some alternate options without the helper function that describe the allocations in more detail:
You could go further and reuse with spare capacity in
optswithout needing to allocate a slice containing the items to prepend (0-1 allocations per function). However this is complex and error prone -- I wouldn't recommend it.