Call me crazy, but I find the sub-optimal “takes a lot of parameters” approach to be more readable, maintainable, and junior dev-able. Don’t get me wrong, I see why that’s not ideal, but currying just seems immensely more complicated and harder to maintain, teach, etc... of course I’ve voided any chance of membership to the “clever programmer club” with this perspective, but I stopped caring about that long ago.
FP has its pitfalls, it is almost too easy to decompose, compose and build abstractions in the FP idiom. We often only praise the power and expressiveness of that property and act as if most of us are capable of making the right decisions all the time, which is quite obviously not the case.
(This is especially true if we add stuff like macros and homoiconicity to the language. The Lisp Curse has been discussed to death.)
I think the best approach here is a disciplined/pragmatic focus on readability as you are implying, without neglecting advanced FP techniques in the places where they actually solve a problem well.
Currying is in a sense one way of doing dependency injection in the FP paradigm. But more often than not, when I'm looking at a function that can/should be curried there is something wrong with the data/data-structures and not with the function.
And instead of bending the function(s) in an 'advanced' (read: complicated/complected) way, we can often transform or even re-model the data itself to make our code clearer and more robust.
The toy examples in the blog post can't be used to illustrate this well, which is fine, because the author just showcases the technique basics.
Indeed, I wanted to show the basics. It's quite hard to find the line between "Easy enough to digest in a few minutes" and "Need to write a book on this topic".
In the end, it just shows you can do this with Go, which is really all I wanted to do :)
Not crazy at all!
I wrote this post with the main intention to show that you can do this in Go, and it might make sense to do this somewhere. I don't mean to advocate this as the 'default way' of programming.
Somewhere in the post I also hinted that there are other solutions, but I wanted to keep the post concise and chose currying as the topic.
Clever programming is about readable and maintainable code, in Go that might mean that (usually) currying is a bad idea. :)
"takes a lot of parameters" is not something to be desired. it usually makes functions hard to use without constantly consulting the documentation. not that a function can't have many inputs, but i'd rather you stick it into a struct, class, or any type of value object in your programming language of choice. a while back i wrote about this problem, out of rage: https://boakye.yiadom.org/bikeshed/funparams/
I do think there are certain niches where this style can make code much more readable (e.g. parser combinator libraries) but otherwise I think it's just a really really poor imitation of proper currying support.
Me too. I can't imagine trying to maintain a project written like this so I'd still consider you a member of the "clever programmer club" in good standing. Ideas like this make sense when the language gives you good support for them like Haskell does:
I remember working in a Go codebase and that was littered with functions that take functions returning funtions that take functions, etc. I couldn't imagine what compelled them to write this way. Now I can see that they just realized first-class functions, dependency injection, and maybe an article about currying. There was almost no way to follow the problem decomposition nor execution path as each aspect rather than being dealt with as encountered just got pushed into a more and more complicated function that eventually executed everything when given the input, an http request!
You see this a lot with HTTP requests. People realize they're often writing the same code over and over and so they pull it out into a middleware of some kind. It's trading off a little readability in exchange for DRYness.
I think even in Haskell, function composition is a bit of second class citizen compared to function application, e.g. "foo (bar value)" has fewer tokens than "(foo . bar) value". There are languages based on composition (concatenative) but few people use them.
The good old wisdom of OO design, single concern, etc. principles usually means that a function that "takes a lot of parameters" is symptom of at least one of these principles being violated.
A struct to collect random parameters, as suggested by other posts, is just brushing this under the carpet.
I don't like many parameters either and I come more from js/php land where mix-types is okay, but couldn't a mixed type hash or array work or just a json object which presumably could allow mixed types since json is a standard but doesn't require all properties to be of a specific type...
I'd much rather have func test(data){
return 'The ' + data.type + ' is ' + data.value + ' and the sum is ' + data.value1 + data.value2;
}
I can easily validate data in the json object or multi-dimensional array or hash or w/e the language I'm working in calls it, and call it a day.
If I need to make it 'understandable' by other devs I'll add a detailed comment section on what all the 'required' data properties are and what not.
In a language like Go you're absolutely right. In a language that supports currying as a first class concept they are equally readable, all the nested functions could be invisible.
Another approach to the monolith func is to have a parameter struct. This offers us:
1. Named parameters (esp useful for literal number or bool arguments)
2. Optional parameters
3. Forwards compatibility as long as new args are optional
This comment makes me really sad. There is so much shit software because developers are lazy or incapable to write clean and concise code. It should not be a problem for any software developer. "Because junior developers" is not a good excuse, they should be capable of learning these things.
While nowhere near the majority of "shit software", there is definitely a subgenre of "shit software" that sources from taking a paradigm from a language you are not currently programming in, and jamming it into the one you are currently working in. If you force currying into Go, you're going to end up with that. This does not clean up your code or make it more concise... in Go.
By contrast, if you're programming in Haskell and you're not taking advantage of the currying, you're missing something. But in that language, you don't manually define a ton of closures, and break the normal convention for calling functions in the language, and break the reflection support, and make the godoc look terrible and confusing, etc. etc., you're using the language as designed. Closure handling is built in and automatic, you're not shoveling out a ton of syntax because it's the default behavior anyhow, it's the only convention in the language, the documentation is designed to handle it, etc.
Another at-least-as-valid perspective is that there is so much shit software because developers feel the need to flex their cleverness and end up making a mess of overly abstract code that can't be safely modified or extended. These developers think other developers have a moral duty to spend time understanding their brilliance, and that anyone who writes simple, flat-footed, readable code is inferior.