I didn't look too much at this but are they offering an alternative to the (IMO ugly) error checking pattern Go enforces? It's interesting to me shoehorning this into Go in particular. Go is notoriously stringent on how you write code.
I'm glad to see this idea getting some traction again. I haven't used Go much in the last few years, but I started playing around with a similar idea back in 2016 when I was working on a small compiler for a configuration management tool, and later put together a small stand-alone proof of concept library(https://github.com/rebeccaskinner/gofpher) as part of a talk (https://speakerdeck.com/rebeccaskinner/monadic-error-handlin...) I gave in 2017.
At the time, I remember finding FP in go surprisingly ergonomic. Implementing the library to support it was a pain since the type system wasn't expressive enough to prevent everything from devolving into a pile of untyped reflection, but it was reasonably easy to keep that an implementation detail. On the whole, I felt like go would have lent itself well to the "dash of FP for flavor" style of programming that seems to be gaining popularity these days. Unfortunately, in 2017 at least, the Go community seemed to have very little interest in the idea.
I still have a fondness for Go. It always felt nice to use. If the language features have caught up to the point where a robust library like this is feasible, and the communities attitude has shifted, I might take another look at the language.
As a Rust guy, I disagree. For one, Rust does not have lazy evaluation, just like C which is why I raised the point C is incompatible with FPP. Lazy evaluation in C (and Rust) can be emulated by function pointers (and closures), but it is not easy to use
I think map() is useful, even if it does not look like Go and rubs a little against the sprit of simplicity of Go. Wish the for loop in Go would return a result, which could accomplish the same but would be a little bit more Go like
x := for y := range z { return y } // unclear return :-(
If you want Either, use Haskell.
There seems also to be a performance problem with map(). It would work better if Go had Iteration instead of slices, otherwise map() creates a lot of slices. And if map does not return a slice you have an ugly
Either is nearly in the language anyways. The vast majority of pragmatic go functions will return [Result, Error] and or just [Error]. We are only missing support to treat this as a monad.
Either as a pattern is _used_ everywhere, but the standard library in go doesn't have one (people just use a tuple), so it's _always_ encoded wrong. It's very annoying.
There is no tuple type in go. When you see a func() (A, error) that is not a function returning a tuple. It is a function returning two values that cannot be composed. You can't A(B()) if B returns two values. It's pure comedy.
I don't think you should bolt every concept into every language. One USP of Go is how dead simple it is to learn and how fast it compiles (because it is dead simple - compare it to Rust compile times). I did a lot of Lisp and Haskell, scaled a startup on FP Scala code and sold it. I did put Option everywhere. In the 2000s I came up with the Iterable hack for Option<> in Java
public abstract class Option[T] implements Iterable[T] { }
// Some then is a one element Iterator/ None is an empty iterator
// With for you then can do something on Some
// orElse left as an exercise to the reader
for (String name: option) {
// do something with name
}
but today I think one should embrace the language and if it does not work for you, use something else.
For Go I think something like Zigs !i32 would fit in perhaps, if one wants a higher level of error handling.
I agree about cramming features across languages. Plus, Software dev is social. I only did this in code for myself.
That being said though, it actually fit really well in golang. Allowed functions that used to return ‘null, err’ to return an Either, which improved on all the downsides of returning null (if you return null your callers have to check for it).
It actually improved the ergonomics quite a bit. ‘Either’ fits nicely into golang, but I doubt it will become mainstream anytime soon.
Never thought I’d see these three things together. The library looks extremely well done, although I’d expect some interesting reaction as Go is as imperative as it gets.
This looks like a pain to modify if you're not intimately familiar with the fp-go library and are just trying to insert a debug statement. Also, the passing two values in parallel via a chain of functions seems really brittle.
It is very probable that data is an Either[Error, Result]. So you are forced to handle the error.
You could also probably add a mapLeft and deal with an error at every step of computation.
Given the way fp-ts work, this library should be very type safe.
But of course, all of this looks much prettier and less verbose in haskell
The example actually does handle all errors, because the values are of type `Either` and the composition functions take this into account.
If you would like to handle a particular error sitation explicitly, e.g. to enrich it with context or to transform one error into another, you may use `MapLeft` or `Swap`.
The real cheat of examples like this is using only existing functions. Once you add a closure with current Go syntax it goes to 11 on the hideous.
There's some chat about adding some variant of (x, y => z) to Go, though even then you're adding some more symbols to an already symbol-heavy structure and it looks even worse when you're not using x y z but (username, accountId => a few lines of username and accountId being used).
I agree that the code will become unreadable as soon as you try to use inline functions (since there are no lambda expressions).
However, the fp style (independent of this library) encourages to decompose the codebase into small and - if possible - pure functions. For testability.
Once the code is structured that way, it's no longer a `cheat`, these pure functions can be used right away in function composition to create more complex structures.
Unfortunately there is no way to use variadic arguments in a type safe way in go (yet) - except for the special case that all arguments are of the same type.
This is why some of the functions that work with many arguments (such as `Pipe`, `Flow`, `Traverse` ...) carry their cardinality as a suffix. Their shapes are then auto-generated up to a max cardinality that seems to make sense in practice.
So the traverse tuple function does not only exist for cardinality 2 but also for higher ones. But unfortunately you have to specify it explicitly.
It feels like they buried the lede, because the referenced Go code is horrendous. I'd be afraid to put that on the front page of my repo, too, if my library lead to code that reads/looks like that.
AIUI, Go intentionally avoids programming language features considered too advanced by its authors in order to lower the bar (to make it easier for most programmers to pick up, that is) and to keep the code uniform, supposedly to the advantage of companies using it. While hammering unidiomatic approaches into a language virtually always is awkward: there are other libraries and programmers, even if you are fine with the rules you have to follow in order to emulate the desirable features using a library. The combination of those things looks even stranger than they do separately.
Love expression-oriented pseudo-FP (F#, Scala, Rust), but I think this recent trend of trying to shoehorn Haskell-lite features into mainstream imperative languages is, to put it gently, extremely awkward.
That said, I actually do remember my first exposure to Golang being a blog post about using monads to avoid incessantly typing `if err != nil`. Very much like that original author, my personal values in software engineering just don't align with Go at all, and that should be OK!
totally agree, for me golang is a strongly imperative language and that's ok. I'm willing to be proved wrong, but I would imagine if you want to do functional programming it's going to be a lot easier to just use a different language.
I'm biased because I've built a career on Go at this point but the pragmatism and ability to just get things done in Go without faffing about with unnecessary abstractions is I think one of the strongest practical demonstrations of how incredible an imperative language can be, and for me personally at least, no FP language will ever beat the productivity that I can achieve with Go, especially because at least in my problem domain the real world problems always have enough corner cases that FP wouldn't even be useful.
In Go I just systematically eliminate and handle each possible step and state, in a straightforward way, directly deal with the business logic, and then it's done and it works predictably and efficiently for years. Interfaces really are a sufficient form of polymorphism, too.
I've always had a hard time breaking into FP paradigms. The basic tutorials feel a bit like math proofs and it's hard to connect the ideas into things I'm actually doing.
As it turned out, I accidentally started learning some functional-lite paradigms in Python. I learned that I actually do like some of these paradigms, and think through them already, I just couldn't connect my internal understanding with the language of FP.
I started learning Rust recently, as it's an exciting systems language with some hype. There, you see even more functional bits which is just a pleasure to use. I'm not in an area where purely functional would make sense but having the quality of life that certain paradigms brings is nice.
I'm still very much a novice in FP techniques, but the ability to try aspects as I go is helpful in learning.
Was just scrolling through the docs. Does anyone feel comfortable with all these generic type annotations? I'm not expert programmer but this looks overkill to me.
This is also one of my favorites, but it can get even more verbose. However this is how the go type system is designed to work with covariance. Without the `~` operator these functions could only be used for a much smaller range of inputs.
But this complexity is an implementation detail of the library, you do not have to understand it as a user of these functions. From my perspective it is a valid approach to move complexity from use application layer into the library layer, so it can be hidden there and tested once.
This is a tour de force, and it accomplishes the goal of enabling FP using the Go syntax and toolchain.
But code written using this library is no longer Go: most Go programmers can't grok it, and it's awkward to call normal Go libraries because there's no way to know if that function you're calling is pure.
If your goal is to "make it easy and fun to write maintainable and testable code in golang" by making pure functions first-class, is there another way to do that without inventing a new language?
From experience and inspired by Carmack's classic essay on FP in C++[1], I tend toward a functional style: minimize state, treat locals as const, avoid non-const globals, enable parallelism by isolating state. Go makes it easy to write static analysis tools, so go vet could be augmented to, for example, keep track of which functions are pure, and show some yellow underlines at those places where input parameters are mutated.
It's syntactically Go but it adds a functional DSL on top of it - that is, you can't just know Go, you need to learn the ins and outs of this library too (plus functional programming) before you can use it.
I cannot recommend this unless you really have to for whatever reason. Besides readability, another factor to consider is performance; Go is not optimized for functional programming structures. It doesn't have things like tail call optimization.
There's better languages than Go if you want to / have to do functional programming.
I believe these things are mostly productivity sinks, which is why I am such a Go fan and also so sad to see these types of projects in Go.
This is exactly what I was afraid of when generics were introduced, and now I get to spend time arguing with people who read some blog post about how functional programming and type theory will save the world, instead of actually being productive. Ugh.
I tend to agree. I’ve done a lot of JS and Python where I end up spending time doing functional tricks basically just because I’m bored and it makes things interesting. With Go when I’m bored I start profiling and making a zero alloc version of a function that only runs once a week.
Do we have to do the "I don't really get it so nobody else should have it" kind of thing. I have a reasonable level of experience with FP and a lot of general experience (I'm not someone who "read a blog post", I've used it commercially) and I find that it's very handy at the right time. So it can be abused like anything else. So people can write over-complex stuff, true, so blame the language because it's easier than addressing the root problem which is people (it's always people isn't it). In a good language (Scala) it really makes a difference.
Please dial back your casual critiques.
> This is exactly what I was afraid of when generics were introduced
Good god. IDK, perhaps hardware would better suit your skill set? It certainly scares me off, but you might have a good mind for it.
I think you should realize is theres a mindset in go. They're highly opinionated about how you structure code, how you format your code (capitalization on all functions as a means to express public/private, tabs, etc), and how they want it to look. It's also designed to be basically a toy language to "make programming easier". They don't want language improvements like this for the most part.
The creator made it because:
"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt. – Rob Pike 1"
> They’re typically, fairly young, fresh out of school
Okay, thanks for clarifying and snakes and ladders is a great game for kids but how long do kids remain kids? They don't and they shouldn't. They don't need constraining for long.
> 're highly opinionated about how you structure code, how you format your code (capitalization on all functions as a means to express public/private, tabs, etc), and how they want it to look.
And as a professional programmer I couldn't give a toss about this, I just want people to be consistent and sensible, and that means useful comments, documentation, a test suite, specs, not going insane on any particular style like OO/FP. You have to trust the programmer in the end cos all the guidelines in the world won't cure stupidity.
However the quote was made by the creator, not me. It's pretty frustrating that the justification and entire philosophy of the language surrounded is "we can't get better at this because it requires work" (Queue up the next few replies to me from people that say "but needless complexity and business value" )
> nd as a professional programmer I couldn't give a toss about this, I just want people to be consistent and sensible, and that means useful comments, documentation, a test suite, specs, not going insane on any particular style like OO/FP. You have to trust the programmer in the end cos all the guidelines in the world won't cure stupidity.
I agree with you. For the most part the industry has had expected language style guide lines (See the Java Style guide) and has had tools that support opting out of guidelines and defining organizational or project based expectations. This is not the case in Go. Gofmt will break your spaces decision and go to tabs. (https://news.ycombinator.com/item?id=7914523) Their response is they don't give a flip.
> Do we have to do the "I don't really get it so nobody else should have it" kind of thing.
Do we have to do the ad hominem thing? I studied FP and type theory in grad school. It's possible to know about FP and not want to use it in software engineering.
Okay, let's do it. ISTM you started the ad hom with loose accusations of people reading blog posts, ignoring that many people actually have solid commercial experience with it and are being "actually being productive" with it, nor did you justify your view they were "productivity sinks", so I couldn't take you seriously.
Then it got even harder with you dissing generics, which are so fundamentally valuable, such a labour-saver, that the idea of programming being better without them is beyond my ken.
So please lay out your case and I'd be willing to talk.
I think most of the stuff in this repo is too much and trying to beat a square peg into a round hole, but the little things like Option and Either patch a hole in Go. I don't think I'd use them without them being in the stdlib, though, which is where such fundamental types belong. Those aren't even really functional, unless you count null pointers as necessarily imperative.
>... read some blog post about how functional programming and type theory will save the world, instead of actually being productive
I see the opposite side in Go a lot, where without testing or trying anything they dismiss everything they aren't already using right now as useless ivory tower academia, which is its own set of popular blog posts. Seems both sides have a lot of time to argue on the internet though, oddly the people who actually write code tend to be the productive ones regardless of philosophy.
Ultimately it is, but I guess my point is that having goofy signatures like these down in the bowels of a library (like FP-Go, or Scala's stdlib) might just be necessary goofiness because FP is FP. (Not to knock FP. Engineering is about tradeoffs.)
Not picking sides here (though disclosure: I use and like FP techniques/tools/languages/libraries).
Go provides a set of nice features (fast startup, easy cross-platform building, great tooling, good package management) that can be hard to come by with other languages. It is not unreasonable to want to have your cake (all of the above features) and eat it too (occasionally use functional idioms in addition to the usual imperative ones).
For this reason I try to keep abreast of the various FP libraries in Go, though I have yet to use one in anger.
Because in some cases the aspect whether or not the language is functional is not the only decision criteria for the choice of language. If it was, I would absolutely recommend to consider a language than go.
But if go is selected because of different criteria (i.e. interoperability with an existing eco-system, cross compilation support, FIPS compliance, ...) then this library can help to apply fp paradigms in you go code. Hopefully to make it readable and testable.
It's an interesting exercise, but this library doesn't fit well into the Go ecosystem's habit of writing straightforward, imperative, boring, verbose, simple-to-read code. Even as someone who likes FP tools, I won't be using this.
This library could be only useful if someone trying to build new Programming Language with its own ecosystem with Golang runtime as backend, like for example Scala on JVM. In upcoming versions Go 1.22 they are also improving code inlining support, so that might help.
Trying to merge this abstractions and patterns with existing Golang's philosophy and community libraries is simply a case of over-engineering.
I don’t know who to feel more sorry for: the junior programmer who has dutifully taught themselves idiomatic go and explicit error handling being shown a large unreadable codebase full of this nonsense on their first day, or the the poor sods having to tear this nonsense out of a large tangled codebase in three years time.
I think a compile to Go approach would have been better. It would allow bypassing most of the warts and still potentially allow interoperability with existing Go code. It would also likely be better received as it would be clear that this is not supposed to be Go.
184 comments
[ 2.4 ms ] story [ 241 ms ] threadAt the time, I remember finding FP in go surprisingly ergonomic. Implementing the library to support it was a pain since the type system wasn't expressive enough to prevent everything from devolving into a pile of untyped reflection, but it was reasonably easy to keep that an implementation detail. On the whole, I felt like go would have lent itself well to the "dash of FP for flavor" style of programming that seems to be gaining popularity these days. Unfortunately, in 2017 at least, the Go community seemed to have very little interest in the idea.
I still have a fondness for Go. It always felt nice to use. If the language features have caught up to the point where a robust library like this is feasible, and the communities attitude has shifted, I might take another look at the language.
Are there any examples you'd be interested in in particular?
Does one need lazy evaluation to be FP?
Scheme isn't lazy to my knowledge.
There seems also to be a performance problem with map(). It would work better if Go had Iteration instead of slices, otherwise map() creates a lot of slices. And if map does not return a slice you have an ugly
everywhere.Not an expert in Go but I think you can do this:
The above is equivalent to haskells fish operator >=>The bind operator (>>=) can be implimented in terms of composition:
You can if A is a function which takes two arguments, e.g.
https://go.dev/play/p/Jp4B0L6NJj2And there are no slices or channels of tuples, return values must be destructured right away.
From my experience, "handling" the error means wrapping it in another error and returning, which is what you get from other languages for free.
For Go I think something like Zigs !i32 would fit in perhaps, if one wants a higher level of error handling.
That being said though, it actually fit really well in golang. Allowed functions that used to return ‘null, err’ to return an Either, which improved on all the downsides of returning null (if you return null your callers have to check for it).
It actually improved the ergonomics quite a bit. ‘Either’ fits nicely into golang, but I doubt it will become mainstream anytime soon.
https://github.com/IBM/fp-go/tree/main/samples
Given the way fp-ts work, this library should be very type safe.
But of course, all of this looks much prettier and less verbose in haskell
People should stop pushing these things already, no one cares but them.
There's some chat about adding some variant of (x, y => z) to Go, though even then you're adding some more symbols to an already symbol-heavy structure and it looks even worse when you're not using x y z but (username, accountId => a few lines of username and accountId being used).
This is why some of the functions that work with many arguments (such as `Pipe`, `Flow`, `Traverse` ...) carry their cardinality as a suffix. Their shapes are then auto-generated up to a max cardinality that seems to make sense in practice.
So the traverse tuple function does not only exist for cardinality 2 but also for higher ones. But unfortunately you have to specify it explicitly.
1. To look clever.
2. To make junior programmers look stupid.
That said, I actually do remember my first exposure to Golang being a blog post about using monads to avoid incessantly typing `if err != nil`. Very much like that original author, my personal values in software engineering just don't align with Go at all, and that should be OK!
I'm biased because I've built a career on Go at this point but the pragmatism and ability to just get things done in Go without faffing about with unnecessary abstractions is I think one of the strongest practical demonstrations of how incredible an imperative language can be, and for me personally at least, no FP language will ever beat the productivity that I can achieve with Go, especially because at least in my problem domain the real world problems always have enough corner cases that FP wouldn't even be useful.
In Go I just systematically eliminate and handle each possible step and state, in a straightforward way, directly deal with the business logic, and then it's done and it works predictably and efficiently for years. Interfaces really are a sufficient form of polymorphism, too.
As it turned out, I accidentally started learning some functional-lite paradigms in Python. I learned that I actually do like some of these paradigms, and think through them already, I just couldn't connect my internal understanding with the language of FP.
I started learning Rust recently, as it's an exciting systems language with some hype. There, you see even more functional bits which is just a pleasure to use. I'm not in an area where purely functional would make sense but having the quality of life that certain paradigms brings is nice.
I'm still very much a novice in FP techniques, but the ability to try aspects as I go is helpful in learning.
func TraverseParTuple10[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], F8 ~func(A8) ReaderIOEither[T8], F9 ~func(A9) ReaderIOEither[T9], F10 ~func(A10) ReaderIOEither[T10], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(T.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) ReaderIOEither[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]
(https://pkg.go.dev/github.com/IBM/fp-go/context/readerioeith...)
cracks knuckles over keyboard
Kill it with fire but make sure to pour some Holy water first.
But this complexity is an implementation detail of the library, you do not have to understand it as a user of these functions. From my perspective it is a valid approach to move complexity from use application layer into the library layer, so it can be hidden there and tested once.
But code written using this library is no longer Go: most Go programmers can't grok it, and it's awkward to call normal Go libraries because there's no way to know if that function you're calling is pure.
If your goal is to "make it easy and fun to write maintainable and testable code in golang" by making pure functions first-class, is there another way to do that without inventing a new language?
From experience and inspired by Carmack's classic essay on FP in C++[1], I tend toward a functional style: minimize state, treat locals as const, avoid non-const globals, enable parallelism by isolating state. Go makes it easy to write static analysis tools, so go vet could be augmented to, for example, keep track of which functions are pure, and show some yellow underlines at those places where input parameters are mutated.
I'd use something like that.
[1] http://sevangelatos.com/john-carmack-on/
I cannot recommend this unless you really have to for whatever reason. Besides readability, another factor to consider is performance; Go is not optimized for functional programming structures. It doesn't have things like tail call optimization.
There's better languages than Go if you want to / have to do functional programming.
That is… literally just how libraries are. You need to understand the underlying language and the semantics and details of the library API.
This is exactly what I was afraid of when generics were introduced, and now I get to spend time arguing with people who read some blog post about how functional programming and type theory will save the world, instead of actually being productive. Ugh.
Please dial back your casual critiques.
> This is exactly what I was afraid of when generics were introduced
Good god. IDK, perhaps hardware would better suit your skill set? It certainly scares me off, but you might have a good mind for it.
The creator made it because: "The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt. – Rob Pike 1"
Okay, thanks for clarifying and snakes and ladders is a great game for kids but how long do kids remain kids? They don't and they shouldn't. They don't need constraining for long.
> 're highly opinionated about how you structure code, how you format your code (capitalization on all functions as a means to express public/private, tabs, etc), and how they want it to look.
And as a professional programmer I couldn't give a toss about this, I just want people to be consistent and sensible, and that means useful comments, documentation, a test suite, specs, not going insane on any particular style like OO/FP. You have to trust the programmer in the end cos all the guidelines in the world won't cure stupidity.
However the quote was made by the creator, not me. It's pretty frustrating that the justification and entire philosophy of the language surrounded is "we can't get better at this because it requires work" (Queue up the next few replies to me from people that say "but needless complexity and business value" )
> nd as a professional programmer I couldn't give a toss about this, I just want people to be consistent and sensible, and that means useful comments, documentation, a test suite, specs, not going insane on any particular style like OO/FP. You have to trust the programmer in the end cos all the guidelines in the world won't cure stupidity.
I agree with you. For the most part the industry has had expected language style guide lines (See the Java Style guide) and has had tools that support opting out of guidelines and defining organizational or project based expectations. This is not the case in Go. Gofmt will break your spaces decision and go to tabs. (https://news.ycombinator.com/item?id=7914523) Their response is they don't give a flip.
Do we have to do the ad hominem thing? I studied FP and type theory in grad school. It's possible to know about FP and not want to use it in software engineering.
Then it got even harder with you dissing generics, which are so fundamentally valuable, such a labour-saver, that the idea of programming being better without them is beyond my ken.
So please lay out your case and I'd be willing to talk.
>... read some blog post about how functional programming and type theory will save the world, instead of actually being productive
I see the opposite side in Go a lot, where without testing or trying anything they dismiss everything they aren't already using right now as useless ivory tower academia, which is its own set of popular blog posts. Seems both sides have a lot of time to argue on the internet though, oddly the people who actually write code tend to be the productive ones regardless of philosophy.
1: https://github.com/scala/scala/blob/v2.13.11/src/library/sca...
"func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int { "
Looks like Scala
Also yes it is awful.
Go provides a set of nice features (fast startup, easy cross-platform building, great tooling, good package management) that can be hard to come by with other languages. It is not unreasonable to want to have your cake (all of the above features) and eat it too (occasionally use functional idioms in addition to the usual imperative ones).
For this reason I try to keep abreast of the various FP libraries in Go, though I have yet to use one in anger.
Trying to merge this abstractions and patterns with existing Golang's philosophy and community libraries is simply a case of over-engineering.