18 ms·
Don't understand why Go developers choose the most complicated solutions. Function returning function returning function is an awful style of coding that is har
by codedokode 4y ago
Don't understand why Go developers choose the most complicated solutions. Function returning function returning function is an awful style of coding that is hard to read. I see at least two simple ways to pass options:
1) named arguments:
createFoo(barness: "bar", bazness: True)
2) struct with default values:
createFoo(FooConfig{barness = "bar"})
Go might not have these features, but I guess it is easier to add them than to invent weird "function returns function" tricks.
With functional options the code needs to be duplicated: first, you need to define a field in a struct and then a functional option that sets that field to a given value. With ideas above no duplication is necessary.
- mixedCase 4y agoGo developers came up with this pattern to deal with the language's limitations. Go's core team would probably advocate for a mutable configuration struct with a magical interpretation of zero values, and/or a constructor-by-convention and advise users to "just not make mistakes". If we had non-zero default values or named arguments, this pattern wouldn't exist.
- tptacek 4y agoThere would still be reasons to have configuration structs if we had named arguments.
- skrtskrt 4y agoAs a former Python dev, default arguments are nice but they get abused to hell. Need to add functionality to something? Don't think! Just add an argument with a default to the current behavior, all the way up and down the stack. Now you have just one API that does everything! Just set 20-40 parameters to decide the behavior.
- codedokode 4y agoPython just gives you a choice, use named arguments or refactor your API and use objects for options. It is up to you how you are going to use it. What I don't like though is when all those options are hidden behind two-stars keyword argument and it is difficult to understand what options are allowed, what they mean, what type they have and so on: def example(**options): ...
- jen20 4y ago> Function returning function returning function is an awful style of coding that is hard to read. Although I hate functional options for their lack of discoverability, the idea that currying is "awful" is pretty far fetched.
- munificent 4y agoGiven that the parent comment says "hard to read", an obvious charitable interpretation of their comment implies "with Go's syntax". Currying in languages that use different syntax is orthogonal to the point being made here.
- codedokode 4y agoUsing currying by itself is fine and easy to understand. But if you write a function that returns a function that returns a function — that's not easy. For example, compare these two functions: function (a, b) { return a + b; } function create_adder(a) { return function(b) { return a + b;}} The purpose of first function is easier to understand. And if you rewrite last function according to modern trends, it can become even less readable: const create_adder = (a) => (b) => a + b;
- jen20 4y agoNo-one would actually do this in real code though - this pattern is typically used for partial application.
- xigoi 4y agoHow is (a) => (b) => a+b less readable than (a,b) => a+b?
- codedokode 4y agoBecause the first is a function returning function, and you have to think how that works, and the second one is just a simple function that returns a sum. Also, if you start a function with a word 'const' then it is less obvious that this is a function and not just a constant. In contrast, if you use a word 'function', then you understand what it is from the first word.
- eru 4y ago> Function returning function returning function is an awful style of coding that is hard to read. Hey, that's basically how any function with more than one argument is implemented in Haskell. (Look up currying.) It's not so much that this style is 'awful' in any universal sense; it's more that Go is terrible, terrible host language for anything in this style.
- codedokode 4y agoHaskell hides this complexity and allows you to write just: add x y = x + y There is no functions returning functions here, just a normal addition. It is perfectly readable.
- eru 4y agoYes, it is perfectly readable. But: there is a function returning a function. Haskell just decided to have a syntax (and type system) that can handle this without inducing nausea in the programmer. As with everything, this comes with trade-offs. For example, Haskell's syntax (and type system) have a much harder time dealing with optional arguments. Something that eg Python does just fine, and that even C handles OK in the form of varargs.
- kllrnohj 4y agoNeither of those address the issue of scoped overrides of a setting. The benefit of the functional options approach is they return the inverse setting, so you can very trivially do scoped overrides for things like log levels. Eg: prevVerbosity := foo.Option(pkg.Verbosity(3)) foo.DoSomeDebugging() foo.Option(prevVerbosity) (better yet, use defer to restore) Your examples seem to be reducing the problem space to exclusively object creation time. And in that case, yes named params or a struct with default values work great. But they work a lot less great when you're talking about changing an existing object, as now your default values can't just be the actual default values, but rather optional values since you need to distinguish between "set to a value that happened to be the same as the default" and "didn't set a value at all, so don't change it".
- iainmerrick 4y agoIs that really such a common case? Obviously it depends what you’re configuring, but I definitely would not expect that it’s typically OK to jump in and modify the configuration of an object that’s already in use. What if it does do some expensive one-off setup using the supplied config at object creation time? If you really need a general scoped override, it could be done in the config struct approach just by copying and restoring the entire config. This might be expensive if the struct is big, but on the other hand, you could change multiple properties at once which doesn’t look possible in the function-based approach.
- kllrnohj 4y agoIf the object has an API to do functional options, why wouldn't you expect it to be viable to do a scoped override? The API for dynamically changing them already implies they are available to be changed frequently. The source of this pattern per the article https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html?m=1 https://commandcenter.blogspot.com/2014/01/self-referential-... The example used is trace verbosity, which is both useful to do a scoped override and cheap to do.
- iainmerrick 4y ago
- unboxingelf 4y agoGo definitely has these features and it’s the standard way to pass config. CreateFoo(FooConfig{bar: “baz”})
- nmilo 4y agoYou're conflating developers who write in Go with developers who write Go. It's easier for me to write weird "function returns function" tricks than it is to fork a compiler.