4 ms·
The whole point of the "functional" options pattern is that the option's own identity or type doesn't matter, only its side-effects. If you do need to examine t
by kbolino 2y ago
The whole point of the "functional" options pattern is that the option's own identity or type doesn't matter, only its side-effects. If you do need to examine the option itself, you're either using the wrong pattern, or you should be using an interface with additional, self-descriptive methods instead.
For example, I have used an interface similar to the following for a custom HTTP client wrapper (as discussed in another thread):
type RequestOption interface {
Priority() int8
Name() string
Apply(r *http.Request) error
}
The Priority method might seem like overkill, but it was useful here because there are default options for the whole client and local options on each call, which have to be merged and applied in the proper order.
- Groxx 2y ago`==` is a useful side-effect, and violating it makes a type rather abnormal.
- kbolino 2y agoIf you're using the functional options pattern, and you're trying to == or type-assert one of the options, you're the one doing something abnormal. Maybe it makes sense in the broader context of what you're trying to do, but then I'd say that the functional options pattern isn't the right fit in the first place. Also, "side effect" in computer science has a pretty straightforward definition, and equality comparisons are not side effects.
- Groxx 2y agoSide effect: agreed, but the way you used it makes no sense in that definition (arguments cannot have side effects on a function call, they're resolved before the function is called), and the main other option in Go is to pass a struct or separate typed arguments which also has no side effects, and the only other interpretation is "the side effects you can trigger in the function based on the argument" which is identical in all scenarios... so I figured you were using it in the more colloquial way where it's closer "observable behavior [of the argument's type]". Or "the unintended consequences of a design decision".
- kbolino 2y agoIn its simplest form, a functional option is: type Option func(ptr *T) The entire purpose of this function is to be called, and when called, to change something about the value pointed to by ptr. That seems like a classic case of side effect to me, since the function returns no value (hence it's not a "pure function") but instead mutates some state (which lives beyond the function call). For example, we can make such an option like this: func SomeOption(optionalArg string) Option { return func(ptr *T) { ptr.otherField = optionalArg } } The options would then be accepted by another function, such as: func Example(requiredArg string, options ...Option) T { val := T{someField: requiredArg} for _, option := range options { option(&val) } return val } In the body of Example, there's no need to use == on an Option. Each Option does its thing when called, and then we move on to the next one. We would use all of these things like this: foo := Example("someFieldVal", SomeOption("otherFieldVal")) Obviously, this is a trivial example, but I think it illustrates the point that functional options are used for their side-effects. If you need to do fancier things than just unconditionally set a field, you do all of that in the option itself (either before creating the closure, or inside the body of the closure).
- Groxx 2y agoYou're describing the absolute basics of functional options to someone who, by cautioning about a niche usability issue with this exact implementation, has demonstrated a solid understanding of them.
- kbolino 2y agoI've re-read everything you've written so far, and I think we're just in violent agreement. It would have helped if you provided some explanatory code, but the word "middleware" in your original post (which may have been edited in? if not, sorry, that's on me) clarifies a lot for me. If you do need to == or type-assert the options, then don't make them functional. That seems to be what you were saying with `type thearg string` earlier. However, I think my original example with an interface illustrated another/better way to solve the same problem: make the options self-describing.