4 ms·
What are the benefits that this functional options style offers? Is it that you can add more options without having to define a new field in a struct? One coul
by spockz 4y ago
What are the benefits that this functional options style offers? Is it that you can add more options without having to define a new field in a struct?
One could even add all the With methods to the struct to get some fluent/builder pattern.
Edit: is it so that you don’t need to instantiate a new struct at each call site?
- codedokode 4y agoI think that there is no benefits, it is just a workaround for lack of named arguments and default values for struct fields.
- Someone 4y agoThe blog post that introduced it (https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html https://commandcenter.blogspot.com/2014/01/self-referential-...) mentions func DoSomethingVerbosely(foo *Foo, verbosity int) { prev := foo.Option(pkg.Verbosity(verbosity)) defer foo.Option(prev) // ... do some stuff with foo under high verbosity. } but I don’t see why that’s better than func DoSomethingVerbosely(foo *Foo, verbosity int) { prev := foo.setVerbosity(verbosity)) defer foo.setVerbosity(prev) // ... do some stuff with foo under high verbosity. } It also mentions that it allows you to “set lots of options in a given call”. That, you could sort of accomplish by having the setProperty methods return the changed object, thus allowing chaining (e.g. foo.setVerbosity(v).setDryRun(true)). This allows both that defer and setting lots of options in a single call. Given the limited features of go, it’s a nice hack, but I don’t like it. To me, it doesn’t feel like it fits the philosophy of go.
- djur 4y agoYour chaining example and the defer example can't both work together, since they both rely on different return types for SetVerbosity.
- nemothekid 4y agoThe main benefit is you can have configuration options without having to specify all values, and also have non-zero-value defaults. Lets say you had something like Sarama's config struct which contains 50 or so config knobs. The following is will lead to some terrible defaults: NewConsumer("kafka:9043", Config{ClientID: "foo"}) Here, with this config, there is a config option `MaxMessageBytes` which will be set to 0, which will reject all your messages. What Sarama does is, you can pass a `nil` config which will load defaults, or: conf := sarama.NewConfig(); conf.ClientID = "foo" conf.RackID = "bar" NewConsumer("kafka:9043", conf) and so on. This is ok but can be cumbersome, especially if you just need to change one or 2 options or if some config options need to be initialized. Also someone can still do &Config{...} and shoot themselves in the foot. The functional options style is more concise. NewConsumer("kafka:9034", WithClientID("foo"), WithRackID("bar")) I used to be a fan of this style, and I even have an ORM built around this style (ex. Query(WithID(4), WithFriends(), WithGroup(4))), but I think for options like these a Builder pattern is actually better if your intention is clarity.
- akira2501 4y agoI don't write a huge amount of Go, but why not something like: conf := sarama.WithDefaults(Config{ ClientID: "foo", RackID: "bar" }) To prevent accidental use, add a "Verified" field that must be set before the configuration is accepted. You could also use the above style in a single shot with NewConsumer if you didn't need to retain the configured struct after creation.
- everforward 4y agoThe problem isn't that users are accidentally using it. People are using it intentionally. The problem is that some of the config fields are mandatory, and some are optional, but the developer can't tell whether the fields were intentionally set to a weird value or just not specified, and users can't tell which fields need to be set to not have strange behavior. You would need to add a Verified field for each config option so you can tell whether users set it or it's a default. Alternately, you can use pointers, but that gets clunky because you can't do conf := sarama.Connect(Config{ ClientID: &"foo"}) You have to declare a variable above, and then reference the var. Zero values are sometimes valid config options, but not what most people actually want to do. Structs cause issues because there's no way to specify just a couple of fields in the struct; the rest will get zero values.
- skrtskrt 4y agoIt also means there's no worry about breaking changes to a `Config` struct
- moron4hire 4y agoBreaking changes are good. They force people to consider the change.