Hacker News new | ask | show | jobs
by zemo 867 days ago
I used that pattern for a while but stopped using it. I first encountered it from this blog post: https://commandcenter.blogspot.com/2014/01/self-referential-...

It's a lot of boilerplate to create something that's not actually immutable. It also makes it harder to figure out which options are available, since now you can't just look at the documentation of the type, you have to look at the whole module package to figure out what the various options are. If one of the fields is a slice or map you can just mutate that slice or map in place, so it's not really immutable. The pattern as Pike describes it has the benefit that supplying an option returns an option that reverses the effect of supplying the option so that you can use the options somewhat like Python context objects that have enter and exit semantics, but in practice I've found that to be useful in a small portion of situations.

2 comments

> It's a lot of boilerplate to create something that's not actually immutable

How is not actually immutable? How could cfg.opts.name be modified after New() returns?

> It also makes it harder to figure out which options are available

I find it easier, the go tooling helps a lot. For example, all the options are grouped together at https://pkg.go.dev/github.com/go-kit/log/level#Option

It's also easy to use "Find Usages" in my editor, and filter on return type.

It could be mutated by anything in the package that contains the type. The only thing Go can make truly immutable - as in a compiler error if you try - is a constant primitive.

Functional options have some niceties - largely their ability to evolve without breaking changes - but as the GP points out, completely break discoverability with intellisense. Having to do some dance with filtering usage to find the options available is just worse than a static config struct with zero values that are meaningful for anything not set.

> you can't just look at the documentation of the type

Sure you can. Option func is a constructor for option type, and constructors are auto-included above methods in the docs.

PLS completion works for them as well.

The options for the thing being constructed are all separate types from the thing being constructed; the options aren’t a facet of the definition of the type they mutate.
I'm saying:

- main constructor is easily available from the main type's docs,

- option type is easily available from the main constructor's docs,

- all option funcs are easily available from the option type's docs (because in fact these option funcs are constructors for the option type).

Excerpt from grpc godoc index:

    type Server

    func NewServer(opt ...ServerOption) *Server

    ...
    ...

    type ServerOption

    func ChainStreamInterceptor(interceptors ...StreamServerInterceptor) ServerOption
    func ChainUnaryInterceptor(interceptors ...UnaryServerInterceptor) ServerOption
    func ConnectionTimeout(d time.Duration) ServerOption
    func Creds(c credentials.TransportCredentials) ServerOption
    etc...
One more hop compared to a flat argument list, that's true. But if you only commonly use maybe 0-5 arguments out of 30-50 available, it does not look like a bad deal.
I’m not confused about what it is, I just don’t like it. It’s a lot of ceremony for very little gain.