Hacker News new | past | comments | ask | show | jobs | submit login
Function Currying in Go (medium.com/meeusdylan)
88 points by Insanity on Oct 11, 2019 | hide | past | favorite | 94 comments



Compared to other languages, I find Go's syntactic support for partial application and currying lacking, and every http.HandlerFunc I weep for the redundancy I create.

I get that that is Go's design philosophy, but IMO a little syntactic sugar would be nice.


GO syntax is very regressive comparing to where we hopped programming languages should go.

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.


I hear you, but there are also upsides. As a veteran of many programming languages, I find Go's minimal feature list a plus!

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.


I find the opposite effect to be true. In many/most cases, the type model of a piece of Go code is not at all obvious without detailed inspection.

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.


I understand where you're coming from, and I've made many a shallow criticism from a place of frustration, but please understand that thats what this is.

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.


No doubt our different perspectives are a result of us doing different things with Go.

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.


>GO syntax is very regressive comparing to where we hopped programming languages should go.

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.


The syntax being regressive is pretty objective. It's not a value judgement. It has it's pros and cons.


I like that I can dive into pretty much any Go code and it won't be doing anything basic some way I'm not used to or can't very quickly figure out without having to look anything up. Kinda like Java in that regard, but even more so, plus way less verbose.

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.


Go is pretty verbose, especially error handling, vs modern Java code.


You and I clearly have different error handling strategies.

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.


I'd rather have the choice to handle it locally or not. If you have bad habits you have bad habits and another language isn't going to fix that. If Go adapts Rust style then I would agree, it could get to be where you can choose to handle the errors locally or up the stack, like exceptions, and you will be in a better place.


It's less about bad habits and more about there being a clear and consistent signal for what can throw or not. It's similar to why I like options over nulls despite the fact that you're basically doing the same thing in both systems.

If only Java's particular implementation hadn't ruined the public opinion on checked exceptions...


What do you think is the best way to handle 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's Result<T, E> and similar options in other languages are by far my favorite, especially with the `?` operator and pattern matching to go along with them.

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?


If experts' code never becomes more clear and concise than novices', there's no payoff for getting better with the language, and nobody ever really gets up to speed.


Big IMHO disclaimer, but:

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.


>there's no payoff for getting better with the language, and nobody ever really gets up to speed.

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.


Novices should see queues and sorted sets by their proper names. They should see reduce(map(filter(…))), not big loops that inline all three written from scratch repeatedly because experts didn't want the compiler doing work that might make it slower.


This will probably confirm your point. I write software professionally for 20 years. I’ve written professionally in: perl, php, java, cfml, actionscript 2 and 3, javasctipt / node, c#, java, erlang, ruby, scala, python and golang. The last year almost exclusively golang. I work full back end / distributed systems and infrastructure automation.

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.


It sucks when you're writing it, but makes up for it when working with other people's code.


Author here!

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. (->, =>)


your first Go example is missing a parenthesis


Thanks, I checked the syntax again and noticed some other places where I missed it. :D


"http.HandlerFunc I weep for the redundancy I create."

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.)


Middlewares force you to use a Go context to pass data, which has its own downsides. I personally much prefer passing data explicitly — through function composition and structs and so on — to contexts.


Specific "middleware libraries" may; the general concept does not. The general concept is just to wrap a decorator around an http.Handler with something else that implements http.Handler. You don't need any sort of specialized "middleware support"; all Go interfaces already support being decorated.

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.


Right, I was specifically referring to routers like Chi where you attach middlewares to a "mux". These don't rely on function composition, and so the only way to pass information onwards — loggers, sessions, and so on — is to use contexts.

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.


I'm getting some serious Java 1.x flashbacks here, so just give it 20 years…


As early Java adopter, I find it ironic that Java is already considered a PhD language from Go users point of view.


It took mere 8 years for Java to implement generics, though.


golang is older than that at this time. They deliberately ignored proven concepts in programming language design for some hand-wavy goal of "simplicity", making unsubstantiated claims about "programming in the large" and "scalabilty" along the way.


>They deliberately ignored proven concepts in programming language design

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.


> Go has generic container types, so woot, no need for plain generics.

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.


"proven" yeah C++ template and Java half baked generics are really proven ...


Java's generics solve vastly more problems than they cause.

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.


To quote some other HN comments: "systems PHP", "If I need a well designed language, I have Rust. Go's sell for me is it's so naively simplistic it's actually useful when your team members are idiots". These are a bit harsh, but sort of hints on the target audience: people who have neither a lot of programming experience, nor a developed taste, but have practical problems to solve without shooting themselves in the foot.


That seems unnecessarily harsh towards Go programmers in my opinion. Languages have different up and downsides and different areas where they shine.

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". :)


Yes; I stated so.

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.


It being harsh does not make it any less true. Go is self admittedly catering to the lowest common denominator of developers.

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.


In 2004... parametric polymorphism was decades old with many implementations by the time Go came around.


You just name all variables with five characters max and save typing on that.


The language is lacking in every other department as well, so it is not a surprise.


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.


I agree with you in a sense.

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:

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.


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.


You can do this, but should you?

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.


Ah yes, the Go mantra. Make your code as dumb and long as possible.


Writing dumb code is often a good thing. Dumb code still solves your problem and can be structured well to keep it readable and maintainable.

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.


Write clever code then dumb it down


Compared to what? I used to do scripting languages and Go code is usually more verbose. Not much more verbose though. Think it's mostly the static typing.

While keeping things short is a goal, it's not the only goal..


Found a “clever programmer club” member.


I sort of agree. I had a project in which I wanted to use Go, but I ended up writing Emacs macros to repeat boiler plate code. That wasn't pleasant, and to me it meant that Go wasn't the most suitable language for that problem.

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.


Go demands a ton of boilerplate but writing it all by hand isn't a good use of time. Lots of people are working on //go:generate tools, much as cfront used to emit awful C boilerplate we would ignore while writing C++.


Well, even if you think it would be great to have a greet function with the first argument pinned down, that is trivially done without currying. Currying would only really be useful if you have many functions you want to pass the “Good morning” parameter into.


> Though we notice we only greet for mornings and afternoons so always having to pass “Good morning” or “Good afternoon” becomes a bit annoying.

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.


The point is about composability of functions though. Your example does it statically. Currying and partial application allows it to be done dynamically within the running program.


Actually my example is based on partial application involving anonymous functions. It's just hand compiled.

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 usually create a struct once my parameter list goes past 3. Then, pass that in. Or, if applicable, create a method instead.


In production code I do this too. The struct approach has various advantages (nice syntax, maintainable, shows the meaning of what you are passing, ..).

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!



String.format is slow compared to normal string concatenation (i.e. "foo" + "bar")! Bit me once when using String.format in a hot path.

https://stackoverflow.com/questions/513600/should-i-use-java...


Those benchmarks can't be accurate as they don't use JMH.

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.


Oh thanks :)

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 ;-)


I mean, is this even currying? Or just emulating currying?

Without it being done automatically, it is an ergonomic loss, readability loss, etc.


Don't confuse special syntax with the concept itself. Currying is merely an equivalence between functions of 2 parameters to functions of 1 parameters that return functions. Doing it explicitly is still currying all the same.


Off-topic: I see 'medium.com' and I don't tap because I can't face the pop-ups and nag modal. Sad to think, at one time Medium used to be the cleanest reading experience.


Might be against the rules, but here's a "me too."

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.


the trick is open devtools click 'application' and click clear all site data. Then refresh, no nags etc...


The site looks lean and clean without javascript.


I see medium and I don't go because most of the content on it tends to be very opinionated


I use the "Make Medium Readable Again" browser extension because of this. Firefox also has a reader view which I use on a lot of other sites.

https://makemediumreadable.com


This post has more votes than medium's ...


I usually hate these kinds of comments but I have to: Please check out dev.to. It's a minimal, fast, developer focussed blogging platform. It's open source, and quite active.


In 3 months time: Off-topic: I see 'dev.to' and I don't tap because I can't face the pop-ups and nag modal. Sad to think, at one time Medium used to be the cleanest reading experience.


All those true, but the site is terrible for knowledge discovering.9 out of 10 articles are garbage, written solely because now it seems every job applicant MUST have a blog.


I really liked dev.to last year, but now the posts are really repetitive, apart from being mainly front-end discusion, I want representation as a back-end dev :C


Dev.to has a lot of promise, and I visit there occasionally.

I still prefer individual websites to blogging platforms, but Dev.to is the best such thing I've come across.


I feel if people/companies could not set up their own sites or blogs for their technical opinion, then that opinion may be less useful to me.


Kill sticky is really simple, but works really well on Medium too: https://alisdair.mcdiarmid.org/kill-sticky-headers/




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: