Reputation: 886
I have the following interface:
type Selector interface {
SelectOne(ctx context.Context, one A) (Result, error)
SelectTwo(ctx context.Context, one A, two B) ([]Result, error)
SelectThree(ctx context.Context, one A, two B, three C) ([]Result, error)
}
and the following implementations:
type Database struct{}
func (d Database) SelectOne(...) (...) {...}
func (d Database) SelectTwo(...) (...) {...}
func (d Database) SelectThree(...) (...) {...}
Then, on top of that, I want to add a cache layer that uses the very nice github.com/hashicorp/golang-lru library:
type SelectorCache struct {
db Database
cacheOne *lru.Cache
cacheTwo *lru.Cache
}
func (c SelectorCache) SelectOne(ctx context.Context, one A) (Result, error) {
cached, ok := c.cacheOne.Get(makeKey(one))
if ok {
casted, ok := cached.(Result)
if ok {
return casted, nil
}
}
fetched, err := c.db.SelectOne(ctx, one)
if err != nil {
return Result{}, err
}
c.cache.Add(key, fetched)
return fetched, nil
}
func (c SelectorCache) SelectTwo(ctx context.Context, one A, two B) ([]Result, error) {
...
casted, ok := cached.([]Result)
...
fetched, err := c.db.SelectTwo(ctx, one, two)
...
}
func () SelectThree(ctx context.Context, one A, two B, three C) ([]Result, error) {
...
casted, ok := cached.([]Result)
...
fetched, err := c.db.SelectThree(ctx, one, two, three)
...
}
As you see, the cache layer is basically the same for each case with the only difference being in the underlying function. If that was Python, I could easily create a wrapper function that passes *a, **kw to the wrapped function. How can I rewrite that so the boilerplate gets gone?
Upvotes: 0
Views: 405
Reputation: 489083
You mention in a comment that the argument types vary.
In general, you can do this:
The run-time version is easier to code and use and is quite flexible, but of course has some runtime cost. Perhaps you are trying to avoid this (which is fine, but brings to mind the old adage about measuring before optimizing).
The compile-time version is what you've written in your example.
How can I rewrite that so the boilerplate gets gone?
For Go 1, there is only one way to do that: write a program to write your program. 😀 This is what go generate
is all about. There is a Go blog post about it as well.
In Go 2, there will almost certainly be generics, and you can actually play with them a bit. They will be the way to do what you wanted.
Upvotes: 1
Reputation: 85780
You could write a variadic function (see Function types) that takes arbitrary number of int
s as arguments(zero or more) and process them in one shot. For e.g.
func (d Database) Select(ctx context.Context, numbers ...int)
You could just iterate over numbers
in a range
for loop and perform your desired actions. Your function invocation can remain the same as before though.
fetched, err := c.db.Select(ctx, one)
fetched, err := c.db.Select(ctx, one, two)
fetched, err := c.db.Select(ctx, one, two, three)
Upvotes: 3