Function Currying in Go
medium.com
medium.com
I get that that is Go's design philosophy, but IMO a little syntactic sugar would be nice.
I'm pretty happy that I can use Go to do these things, but you are absolutely right that without the syntactic sugar it gets quite messy to read!
Lambda expressions - sure you can do them - but it'd be nice if you also had the syntax we came to know from other langs. (->, =>)
I believe GO success is partly because in recent years, we have many new specialized engineers that programming isn’t their main focus (DevOps, Data Scientists, SREs) and naturally they are looking for tooling with shorter learning curve.
Also when you consider how many developers have a hard time to learn GIT after so many years, you realize why companies need something as simple as GO comparing to Rust, or Java comparing to Scala for instance.
Speak for yourself maybe, but I appreciate Go's overall style. You also mention Rust and Scala in your post. If that is to be the future of language syntax, I should quit.
I mean it's gotten better but take Javascript where just a few years ago fundamental branching & execution order and might be provided by some library and it wasn't always the same one (thinking especially of various competing promise libraries) or friggin' imports might look different. Or Rails where you might have a codebase come across your laptop that's in some version you haven't used recently and using a bunch of libraries you're not used to and now half the symbols in a given file aren't defined there or anywhere else that Grep can find and it's unclear where they even come from, so it's off to the docs for a bunch of abandoned gems to get any sense of what's even going on.
Go mostly avoids that sort of deeply unproductive junk. It does achieve that by not providing certain things, plus static typing (thank god for TypeScript, finally some sanity). I'm OK with that. I don't use it as my daily driver but I'm never even a little sad to drop into or contribute to a Go codebase, and I don't need a huge margin of uncertainty on the question "how fast can you get up to speed with this?" for Go code, which I can't say of just about any other language, sight-unseen.
I will say that a rust-style error propagation operator would cut verbosity even further. (Though that is harder to do in go with their choice of multiple return over sum types.)
Still, try/catch seems far more verbose for common error handling strategies to me. And I'd rather see an acknowledgement that something can fail as close to that thing as possible, rather than hunting thorough the stack to find some global try/catch block that silently swallows the error in order to cut down on verbosity.
If only Java's particular implementation hadn't ruined the public opinion on checked exceptions...
This comes up all the time on HN and I've personally landed on preferring Java's checked/unchecked system from anything else I've seen. I get that sometimes you should handle your errors in the small, like Go's errors or Rust's Maybe (or Try?) styles might prefer, but I hate not having any exceptions options to "let things crash".
Rust and golang do both also contain a `panic` that allows you to just "let things crash". But by being separate from the standard methods of error handling, they tend to be used in actual exceptional circumstances.
I'm not a fan of exceptions in general, but a system that had exclusively checked exceptions, but didn't require such verbosity would be nice. Perhaps an annotation that says a method can throw, and any method without that annotation cannot throw?
Yes! If expert code and novice code look pretty much the same, that is great!
I think rather than futzing around with the language, go frees you to think harder about the problem domain.
Better architecture, design decisions, problem domain modelling, documentation / communication, core data structure and algorithm selection, api design - these are the things that distinguish expert from novice output, not statement level expressiveness.
There's obviously a line to be drawn somewhere! - an expert can write "good" code and develop useful systems in GW Basic, but few would voluntarily choose to in 2019. Really though, the runtime limits the problems you can solve in GW Basic more than the language syntax.
I think this is why go focuses a lot on the runtime - low latency gc, concurrency, etc - at the high end of programming ability the limits on what problems you can solve in a language (without dropping down / calling out) are dictated by the power of your runtime, not language syntax.
As a data point, when I think about programmers with the most impactful software outputs (rather than talks, essays etc) it's hard to overlook the C programmers. Git could have been written in any number of languages but fairly straightforward C is enough to get the job done.
The "expert" work in git exists at a much higher level than the source code.
Or maybe it's an indication that I should spend less time playing code golf and more time building what I actually set out to build.
I'd imagine an expert could write a better, performant database than a novice and the fact that can be achieved in language that will effectively communicate to a novice is arguably a good thing.
Especially if I'm not rushed, I have an annoying tendency to try for the most elegant, succinct, idiomatic or general way to program a solution, and given a sufficiently expressive language I then fall victim to analysis paralysis.
By giving me so few options, Go removes a major cognitive load. Very often there's a single obvious way to get something done and I'm not tempted to try anything fancy.
When pretty much anything can implement any interface, and one can only tell whether an interface is fully implemented by close examination of a whole source file (rather than reading a simple implements keyword), the cognitive load is far greater.
The lack of an implements is not just a matter of "less syntax fever!" but one that facilitates other language design choices: in particular static compile time duck-typing. Among the "everybody knows or knows-about" class of languages this is pretty fresh. You can go back and forth on the point of this (or the value of the trade-offs) but it's pretty key to Go's flavor. As a Java geek, I'm pretty familiar with the way you'd prefer and I like it quite a bit too, but once I'd tried it, I've really missed Go's flavor more than a handful of times.
Static duck-typing has some great downstream ecosystem effects. For one, it really facilitates smaller, tight interfaces. If my code uses a type A from another package B, and A has 20 functions that in my code only uses 3 of, I can create an interface on my end with just the 3. Then any testing or any alternative implementations apply only to the minimal interface. If that alternative implementation is by another author, it can interop with my code without importing type A package B. Obviously you don't need this all the time but it's killer when you do.
I don't write compilers or other "highbrow" Computer Science-heavy programs. I most frequently find myself writing interface software, data converters, text extractors, near-system utilities. Lots of the kind of things some people might write in C or Perl. As a result, my usage of data types, even structs, is pretty light. Turns out I can do most of my work with the built-in types: integers, characters, strings, arrays and hashes. I rarely even need/use interfaces!
Data types don't play a big role in my work. Your mileage may vary, obviously.
I find golang simplicity liberating, especially now with GO111MODULE.
No need for stuff like node’s package.json, no maven’s project.xml (I have no problem with maven), no sbt, small native binaries (this is where golang, in my head, beats erlang), simple type system. Instead of focusing on designing the ins and outs of the architecture, it allows me to focus on the flow, like erlang! The structure, design, come later, with proper unit and integration tests.
I love it, it is so simple to switch from r&d mode into production grade system development.
There's plenty of people with a lot of programming experience writing Go, and whose taste might just have diverged a bit from the "mainstream". :)
This is a cultural clash; there is an ample demand for a language with explicitly and harshly limited expressiveness, to prevent the C++ problems from ever happening. Go also is a safe enough language so I'd take it over C any moment where possible.
And boy it works...
I have to write a lot of Go myself. I detest it, and use it only because the industry dictates so.
Yes, I'd rather Sun go with an ML lookalike in 1997, but such was the intellectual fashion, and also computers were bitterly slow at the time, so a compiler had to be simple.
Not really. Generics, as a standalone feature, is not useful for much more than just container types. Go has generic container types, so woot, no need for plain generics.
Languages like Haskell which utilise generics for more useful things do so with other features. In Haskell's case, it's typeclasses, in other languages, it might be operator overloading. If you have a type T, you can't do shit with it unless you know something more about it. A type T that has a notion of + though can be checked at compile time and functions can "add" generically.
Well Go has interfaces. You can stick an Add() method on all the structs you might want to pass "generically" to a function, and add them.
So indeed, Go certainly is much simpler for having skipped "proper" generics, since they get to skip all the complicated stuff that makes them actually useful.
There are dozens of common types that give the lie to this (the standard Go claim), like sync.Map, sync.Value, lru, or pool.
Type classes are a feature that arises from having generics, not one that you need to take advantage of generics.
Are you properly using the Template pattern, and creating middlewares that factor out repeated bits?
There is some stuff that doesn't work for very well, but it does work for many things and it's easy to miss. (Go doesn't have many tools. You must learn to use the ones you have to the fullest. If you do, the remaining gaps are much, much smaller than commonly supposed.)
I have found that there is a bit of a misconception in the Go community that there's something special about http.Handler and that you need lots of special code to deal with it. I've seen the misconception that "middleware" is something special and that you can only do it to http.Handlers a lot, but a "middleware" is just "a decorator applied to http.Handler". You can decorate any interface value the same way. There's also a misconception that "routers" are something special, like, they have their own type or something. But they aren't. They're just http.Handlers that can examine a request and figure out which other http.Handler to call. One thing I do a lot is wrap my top-level logger around the "router" that is the top level of my website; in one shot, you decorated the entire website. It's all just http.Handlers, there's no "real" distinction.
The community has a lot of nice middlewares that do things like basic auth, session management, cache headers, etc. If you use decorators and not middlewares, you'll have to write your own middleware code.
https://www.haskell.org/tutorial/functions.html
In languages like Go, you'll write much more "composable" software by sticking to what the language gives you instead of trying to force this in.
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.
In the end, it just shows you can do this with Go, which is really all I wanted to do :)
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. :)
A struct to collect random parameters, as suggested by other posts, is just brushing this under the carpet.
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.
1. Named parameters (esp useful for literal number or bool arguments) 2. Optional parameters 3. Forwards compatibility as long as new args are optional
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.
Solutions like this, more abstraction to avoid repetitive code, are usually much worse than the problem.
> Though we notice we only greet for mornings and afternoons so always having to pass “Good morning” or “Good afternoon” becomes a bit annoying.
Maybe? But one can do much much much worse than "a bit annoying"
> It would be great if we could create an instance of our “greet” function with the first argument pinned down.
No.
While keeping things short is a goal, it's not the only goal..
I feel like code even should be dumb most of the time and only clever when needed. The hard part is to figure out when to be clever.
That applies here too: if you find yourself writing too much boilerplate code in favor of an abstraction, try another language. If you want/have to continue in Go, look for another solution.
There are solutions for this specific example not involving currying, like making a simple convenience wrapper:
func greet(greeting, name string) string {
return fmt.Sprintf("%v %v", greeting, name)
}
func goodMorning(name string) string {
return greet("Good Morning", name)
}
Or just binding "Good morning" to a variable like gm and using that in repeated calls to greet: greet(gm, "This Person"); greet(gm, "That Person"). That is still reasonably ergonomic, and avoids repeating the greeting string.I manually lifted the lambda to the top level, and de-anonymized it with a name. Then I realized that the only captured parameter had a binding to a string literal "Good Morning", so I propagated that literal into the closure body, eliminating the variable, and that variable's environment.
It ended up looking static due to the static literal, basically.
I like functional programming hence the blogpost, and I like that I can do this in Go. There are places where you might want to do this but I'm not going to advocate doing this _every chance you get_.
But hey, Go lets me write how I want and that's pretty cool!
https://docs.oracle.com/javase/8/docs/api/java/lang/String.h...
I wasn't sure anymore about how I'd do it in Java so I wrote "a language that looks like Java". Took the easy way out instead of having to actually look it up. I'm going to leave it as it is though but good to know ;-)
https://stackoverflow.com/questions/513600/should-i-use-java...
Edit: i do believe you that + is faster, as it doesn't need to parse the format string, but you should still always do benchmarks with JMH or they won't be accurate.
Without it being done automatically, it is an ergonomic loss, readability loss, etc.
I've been avoiding Medium even before they became so blatantly commercial. Forcefully urging me to create an account and log in just to read a bloody article? No thanks.
I still prefer individual websites to blogging platforms, but Dev.to is the best such thing I've come across.