Hacker News new | past | comments | ask | show | jobs | submit login
FP-Go: Functional programming library for Golang (github.com/ibm)
223 points by arpanetus on Aug 18, 2023 | hide | past | favorite | 172 comments



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.

I'd use something like that.

[1] http://sevangelatos.com/john-carmack-on/


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.


> you can't just know Go, you need to learn the ins and outs of this library too… before you can use it.

That is… literally just how libraries are. You need to understand the underlying language and the semantics and details of the library API.


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


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.


I'm not sure how I'm replying to your message.

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.


Okay, by stating their position I took it that you were supporting their position, which I see now was my misunderstanding. Gotcha, and thanks


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


FP totally works in Python...until you realize how slow function calls in Python are.


Feels like the spirit of Scala reborn.


This is basically lodash all over again. Now I need to know two languages to read your code.


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.


Big time agreement here as well.

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.


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.


As much as I love functional programming(f# being my fav). I'd be pissed if I opened a go file and saw code like what's presented here.


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.


Would be a lot more interesting with some usage examples or tests.


There exist at least some examples as part of the go docs, e.g. here https://pkg.go.dev/github.com/IBM/fp-go/either#pkg-examples or https://pkg.go.dev/github.com/IBM/[email protected]/array#pkg-ex... but there certainly could be more.

Are there any examples you'd be interested in in particular?


Seriously, why? The only compelling argument for monadic effect systems I see is in languages with no easy-to-use and lightweight concurrency, and this is where Go shines. I thinks this is cool and all but I don't think it can ever be justified with this added complexity in Go. I have worked much with Cats Effect in Scala, which is nice but it adds some serious cognitive overhead.


One of the design aspects is to make a distinction between functions with and without side effects (pure). In a way to tell these apart by looking at the function signature without having to read the function body. The library uses a function signature without an input but with an output for this purpose. Aside from the trivial case of a constant function, such a signature can only mean that the function has side effects.

type IO[A any] func() A

If you consider this a valid approach, then the set of monadic helper functions make it easier to compose these effectful functions with pure functions.

This article https://betterprogramming.pub/investigating-the-i-o-monad-in... contains some more detailed reasoning.


The long README needs some usage examples.


100%. That's the first thing I want to see after a brief intro.


True, however the README does link to this:

https://github.com/IBM/fp-go/tree/main/samples


  data := F.Pipe3(
   T.MakeTuple2("https://jsonplaceholder.typicode.com/posts/1", "https://catfact.ninja/fact"),
   T.Map2(H.MakeGetRequest, H.MakeGetRequest),
   R.TraverseTuple2(
    readSinglePost,
    readSingleCatFact,
   ),
   R.ChainFirstIOK(IO.Logf[T.Tuple2[PostItem, CatFact]]("Log Result: %v")),
  )
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.


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.


This example hurt my brain a little.

People should stop pushing these things already, no one cares but them.


And, after all that, they don’t even handle the IO errors.


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


Is there no way to access all arguments or rest arguments in Go? Why limit map and traverse tuple to 2?


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.


I always wonder why some people go to a new place and then want to make it like the place they came from.


Go is not a functional language and its generic type system is very restrictive. Why not use a functional language to begin with?


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.


A simple alternative is the combination of:

- https://github.com/samber/lo

- https://github.com/samber/mo

The split is also nice as you can choose to just use the generic convenience functions from lo without the more FP related things from mo.


Step 1: Make a beginner friendly language with minimal syntax and nice concurrency primitives.

Step 2: Add “Monoids for the Endomorphism where the `concat` operation is the usual function composition.”

Step 3: …

Step 4: Profit?


As much as I like the functional paradigm, I don't think it will work well on Go due to two simple reasons:

1) Go doesn't have a concise lambda expression. This makes the functional approach in Go will be more verbose and less readable than the traditional imperative approach.

2) Go's type inference is not sophisticated enough. Most of the time you will still need to explicitly annotate the types, which, again, makes it more verbose and less readable.


1) I would argue that with fp style you might want to structure your code into small, pure functions, anyway, for testability reasons. Also many functions from the go library are already pure functions with one input and one output, so they can be used right way, e.g. `Atoi` from `strcov` or `ToUpper` from `strings`. Also go has the nice feature that you can pass methods on structs (member functions) from their instance as functions (the instance reference is bound in a closure). So lambda functions are not really needed that much

2) I absolutely agree, type inference could be better. However this has improved over time and go1.21 has also made good progress. I would expect that type inference will continue to improve in the future. This library tries to easy the pain of having to specify types redundantly by carefully choosing the order of type parameters. Those parameters that can be inferred (e.g. because they are part of the immediate function argument) come last, whereas the parameters that cannot be inferred come first, so you only have to specify those. This compromizes on a consistent type ordering, preferring useability over (internal) consistency. Examples are `Left` and `Right` of the `either` package. The order of type parameters is reversed between the two to avoid excessibe typing.


Sorry for the ad-hominem, but... it looks like something IBM would do in 2023.


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.


Why, what's wrong with

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


Exactly, I can’t wait to force this on my junior programmers and make them look stupid.

cracks knuckles over keyboard


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.


Blasphemy.

Kill it with fire but make sure to pour some Holy water first.


Wow, this looks like Rust


oh my, its horrible:

    client := H.MakeClient(HTTP.DefaultClient)
    readSinglePost := H.ReadJson[PostItem](client)
    readSingleCatFact := H.ReadJson[CatFact](client)
    
    data := F.Pipe3(
      T.MakeTuple2("https://jsonplaceholder.typicode.com/posts/1", "https://catfact.ninja/fact"),
      T.Map2(H.MakeGetRequest, H.MakeGetRequest),
      R.TraverseTuple2(
         readSinglePost,
         readSingleCatFact,
      ),
      R.ChainFirstIOK(IO.Logf[T.Tuple2[PostItem, CatFact]]("Log Result: %v")),
    )
    
    result := data(context.Background())
    fmt.Println(result())

https://github.com/IBM/fp-go/blob/main/samples/http/http_tes...


Actually, this sample is pretty easily followed and readable to anyone familiar with iterators, tuples, filter, map and transduction. Sure the stuff like Pipe3/Map2 is irritating because Go doesn't have function overloading.

Now, try and do the equivalent in "normal" Go code - it will be 3x-5x the lines of code. (Probably more)


it has no error handling. also look at the API, what a joke:

https://godocs.io/github.com/IBM/fp-go/function

and check out the "constants":

https://godocs.io/github.com/IBM/fp-go/function#pkg-variable...


This library is almost a copy of fp-ts library (TypeScript).

You are forced to handle errors. The result is almost certainly an Either[Error, Result].

fp-ts in TypeScript is not syntactically nice but it feels simple enough that after a week you can be pretty comfortable with it. Although, TypeScript compiler might be much better at inferring types than Go. My experience with fp-ts is that most of the time I do not have to write any type annotations except the top level ones.

The API looks fine, you will find similar type annotations in fp-ts and in Haskell, that's just how handling variadics works in most languages and for useful abstractions like traverse/map/chain you need to have the variadic ones available to avoid having to deal with arrays of anonymous functions (that might all need to have the same type).

When you spend a significant time writing fp-ts you barely look at the types. The experience of writing the code is smooth. Code written still has similar pitfalls as regular imperative programming, pyramids of doom, readability, most of the functions "annotated" with async but they are pure or can be pure if you order the data better etc.

I would say there is friction in the beginning, but as time passes, the brain learns how to parse the code. The effect felt very similar to me when I shifted from colored syntax to just plain black on white. After some point brain does its magic.


It actually does have error handling. It uses a Result type

    type ReaderIOEither[A any] RE.ReaderIOEither[context.Context, error, A]
In fact the test code you linked actually even does a check on the result:

   assert.Equal(t, E.Of[error](count), result())
In order to avoid all those excessive functions and 'silly' constants, Go needs to support const and variadic generics like C++ does. Then the API would become quite clean.

I am not supporting the use of this library in prod code used in a large team - but its OK for small tools where one needs to iterate quickly. Folks familiar with FP constructs (esp users of fp-ts) would follow this code almost immediately. Basically the dirtiness and pain (most of it) has been encapsulated into the library.


> In order to avoid all those excessive functions and 'silly' constants, Go needs to support const and variadic generics like C++ does.

oh yes, lets look to C++ as a shining example. lets please not ruin another language by bolting on functional programming. if you want functional programming, use a functional language.

If you want to use Go, just use a for loop like everyone else.


> If your pure function can return an error, then it will have a (T, error) return value in idiomatic go. In functional style the return value is Either[error, T] because function composition is easier with such a return type.

This seems flawed. In idiomatic Go, T and error are always independently observable. The Either monad implies that they are dependent, which is not true.


I spent about 6 years writing Go at $dayjob, and while what you're saying is technically true, idiomatically you also generally wanted to avoid scenarios where you would _want_ to observe them independently. The standard behavior is is if `err != nil`, the result should be ignored.


That is incorrect. Idiomatic Go is abundantly clear that values must always be useful, even if all you have is the default value. Thus T must be useful regardless of the state of error, and vice versa. As such, they are free to be observed independently. That does not mean T and error cannot be in a relationship, but they are not dependents.

There being a relationship between T and error is common, but observance of error is only significant when the error is relevant. Quite often it is, but not always, and in the latter case you can, assuming the code is idiomatic, safely use T and ignore error. T must be useful, after all.

It may be possible to create a scenario where T is not useful when error is not nil if you really want to screw with people, but that code would decidedly not be idiomatic. Indeed, there is always some way to screw with people if you try hard enough, but that's really beyond this discussion.

The use of the Either monad here is trying to cover a dependency which doesn't exist.


This is nonsense. This isn't about idiomatic Go or not, there is only one way to do things in Go, so a function doing things in that one way doesn't communicate anything to the caller. If you try to open a file, and the file doesn't exist, you have to return a useless nil pointer alongside the error and there is no way to magic up a "useful" T. Usually err != nil means T == nil, so trying to blindly use T assuming it's "useful" will panic and crash your program.

The idiomatic Go way to work around this is to write comments saying "sometimes T is non-nil even if err is non-nil, you need to handle this" and hoping your callers read your comments.

Funnily enough, your philosophy is far more true in a language with proper sum types. In Haskell/Ocaml/Rust, returning a tuple of (T, error) does mean that both T and error should both be "useful", because if they weren't the function would have chosen to return one or the other but not both. You're reading meaning into Go code where meaning can't be present, because there's no choice to be made, and ignoring languages where you actually can have the semantics you want Go to have.


> you have to return a useless nil pointer

nil is useful. Notably, you can derive meaning from its nil-ness. If you try to open a file and it doesn't exist, returning a nil handle is quite reasonable, and one can check for the existence of that handle without needing consider the error.

If, say, you returned an invalid file descriptor when the file could not be opened, conceivably that could make the handle useless, but that would not be idiomatic. That would just be a terrible API design and unkind to the users of your API.

> Funnily enough, your philosophy is far more true in a language with proper sum types.

Of course. But not the Either monad specifically, as its intent is to communicate a dependence between two variables. That can be useful in some languages where variable dependence is a convention, but that is not applicable to idiomatic Go.

Frankly, the only thing funny here is the idea that it is useful to reply to a thread before reading it. Let me reiterate: Either is not a suitable representation of (T, error). They have very different semantics. There are data structures which can serve as a suitable representation of (T, error), but Either is not it.


>Most notably, you can derive meaning from its nil-ness.

This is sophistry. If I try to "use" a nil pointer I get a crash. I have to carefully check that it's non-null even if error is null. You can "derive" the same "meaning" from Result[T, error] being an error instead of a T. You can "derive" the same "meaning" from Option[T] being empty. There is no special meaning that a null file gives me that I can't take from a Result containing an error.

There isn't some big philosophical difference that Go is taking a principled stance on, just a practical one: with those you get type safety, and if you do it wrong you get a compilation error. In the Go way if you do it wrong you get a runtime panic.

>that would not be idiomatic. That would just be a terrible API design and unkind to the users of your API.

That is the vast majority of the stdlib and the vast majority of all popular Go libraries. If idiomatic Go code is code where (T, error) means T is always a useful value even if error is non-nil, then there is vanishingly little idiomatic Go code in existence.

>as its intent is to create a dependence between two variables.

This is nonsense. To use your personal specific terminology, Either encodes a dependence between variables that already exists, it doesn't create it. That dependence exists in Go too, Go isn't a language where the fundamentals of programming change, it's the same in C where people write methods that take both a result and an error pointer.

Either is an option to use when there is a dependence. If there isn't a dependence, and both are always present, you can and should return (T, error) and not Either[T, error]. No one is trying to force Go to always use Either when (T,error) would be appropriate, just like you are not forced to in other languages. You just have choices in those languages you do not have in Go, and overwhelmingly people choose more appropriate types than (T, error) when given the choice.

responding to your edit: >Either is not a suitable representation of (T, error). They have very different semantics. There are data structures which can serve as a suitable representation of (T, error), but Either is not it.

It's odd that you acknowledge this, but then claim that I somehow claimed the opposite. Perhaps you should follow your own advice about reading. Either represents a subset of the four cases that (T, error) covers, and even in Go the two cases that Either covers are the only ones in the vast majority of usage. In Go, most, but not all (and no one is claiming all), uses of (T, error) would be better expressed as Either[T, error].

In fact, the different semantics is the entire point. The point is not to keep the semantics the same but change up the syntax. In Go (T, error) is used commonly, in idiomatic Go unless the stdlib is unidiomatic, to emulate the semantics of Either[T, error]. If (T, error) doesn't have the right semantics for your program - and rarely are all four cases considered - then a more appropriate type with matching semantics should be used instead.


> I have to carefully check that it's non-null even if error is null.

Yes, that is true; at very least you need to read to documentation to understand if there is a relationship or not. Whereas Either defines an explicit dependence between two values, freeing you from that. With that, clearly they cannot be equivalent representations. I am surprised this is not obvious to you.

Honestly, I don't know what the rest of that gobbledygook is all about. It reads like one of those weird posts by Rust users we keep seeing where one is wallowing in the sorrow of not being able to grasp Haskell.


>As you explain yourself, they cannot be equivalent representations.

That's the point. They are not equivalent, they represent different things, and Either is a better fit for the actual code even in Go most of the time. Go shouldn't force people to use (T, error) when Either[T, error] is the correct choice.

But that's twice now you've resorted to ad-hominem attacks instead of responding to the content, so I'll take your implicit admission that you have no rebuttals.


> That's the point. They are not equivalent, they represent different things

Good. I'm glad you came back to the first comment in the thread. I am not sure why it took you so long, but I respect that you got there eventually.

> I'll take your implicit admission that you have no rebuttals.

Naturally. You finally realized what I said is true, and we've talked about nothing else. What could there possibly be to rebut? You have clearly not thought this through.


I recommend you read your own posts sometimes. Your claim of "in Go values must always be useful" was rebutted multiple times. That you're trying to move the goalposts to something neither of us was arguing speaks loudly. You also dance dishonestly around the actual point I made in that last post. Again, I'd recommend following your advice about reading a thread before responding to it.


> Your claim of "in Go values must always be useful"

I said no such thing. I said idiomatic Go indicates that you must always make values useful. This is an onus placed on the programmer, not something guaranteed by the language. However, since the Either monad here was said to be introduced to wrap idiomatic Go code, not whatever haphazardly written Go code you happened to find in a SourceForge repository, that is of relevance.

> was rebutted multiple times

Cool. I must not have read it. There was a lot of weird shit in there that had nothing to do with anything. I'm not sure how that would even find relevance to the discussion taking place. I get that first year computer science programs are starting up and you're excited to share what you learned in your first week, but I really don't care. You are not going to tell me anything I haven't heard many times before.


>whatever haphazardly written Go code you happened to find

Well, if the Go stdlib is haphazardly written and unidiomatic then I think the burden is on you to demonstrate that idiomatic Go, as per your definition, exists.

>more baseless ad-hominem and ignoring the points

I'll boil it down to one sentence: Go forces programmers to use (T, error) when it is not appropriate, and Go would be better if it did not.


> Well, if the Go stdlib is haphazardly written and unidiomatic

Yeah, the older parts of the standard library are definitely not idiomatic. Lots of functions in the stdlib which return errors don't even return an error type. But, of course they aren't idiomatic. Idiomatacy is emergent. They couldn't possibly have been written idiomatically.

> I'll boil it down to one sentence: Go forces programmers to use (T, error) when it is not appropriate, and Go would be better if it did not.

That may be true, but of no relevance. I can see why I didn't bother reading it. Thanks for clarifying that it would have been a waste of my time.


So you've defined idiomatic Go code in your own way, that no one else's definitions match with, such that no idiomatic Go code actually exists. If no idiomatic Go code exists, then it's definitionally true that all idiomatic Go code is without flaws, but I don't agree with your personalized definition of idiomatic Go in the first place.

>That may be true, but of no relevance.

So you agree with this, and it rebuts the entire contents of the first post I responded to. I wonder why you were posting irrelevant, off-topic content yourself. But I'm glad you finally agree that Go isn't flawless.


> I don't agree with your personalized definition of idiomatic Go in the first place.

"That's the point. They are not equivalent, they represent different things" – Oh snap. Well, so much for that silly tangent.

If you want to actually have a discussion, you are going to have to go and understand the discussion that was taking place before you tried to take it in some weird and nonsensical direction. The non-sequitors may be entertaining, maybe even true, but off in la-la land with respect to the conversation taking place.

If your intent is simply to be my personal jester, then by all means, run with it. I'll continue to enjoy the laughs. Most people would pay good money to see an entertainer of this caliber, and you are offering it to me for free! I feel privileged.


Since you seem unwilling or unable to follow an argument you started this seems more like an attempt to bait a response that you can report to the mods instead of an honest conversation. If you want to know why the alleged tangent was relevant, well, the posts are still there.


Just in support of your position, the Google Style Guide says the following:

> If a function returns an error, callers must treat all non-error return values as unspecified unless explicitly documented otherwise

It is absolutely not idiomatic to give any meaning to non-error values under error conditions in the usual case. That is extremely unusual and would be generally confusing.

https://google.github.io/styleguide/go/decisions#returning-e...


> It is absolutely not idiomatic to give any meaning to non-error values under error conditions in the usual case.

Based on what? We have come to see that it is beneficial to make zero values useful. In fact, Go Proverbs even says so. Likewise, we have learned it is useful to return the zero value when you have an error. Most commonly, this means returning nil, which is packed full of all kinds of useful information. Therefore, the T value can be expected to useful if the code is idiomatic.

I'd love to see some real-world code you think is idiomatic, but doesn't return a useful T value when there is an error.

> If a function returns an error, callers must treat all non-error return values as unspecified unless explicitly documented otherwise

This is not at odds with that. This merely warns that not all code you may call will be idiomatic. In fact, if I recall correctly, doesn't os.Open (maybe os.Create) return an invalid file handle in some error cases? The standard library is old and what helped us eventually see what is idiomatic. It is decidedly not idiomatic for the most part. If you rely on a function being written idiomatically, then you are going to run into trouble, as that is not a guarantee (unless the documentation provides such a guarantee).

But in the case of this Either wrapper, it explicitly states it is for use with idiomatic code, not any old code you can throw at it.


>Based on what? ... In fact, Go Proverbs even says so.

And the Go style guide says otherwise: if err is non-nil, you shouldn't even check the other return values. The Go proverbs are just words, it doesn't make them true or even good ideas. When Go proverbs don't agree with how Go code is written, including idiomatic Go code, reality wins over a bad theory. The Go style guide happens to better match real code written by real people, even the people directly responsible for the proverbs.

>I'd love to see some real-world code you think is idiomatic

To be fair, I have been talking about the Go programming language and idiomatic code written in that language. You seem to have a different idea of "Idiomatic Go" from everyone else's. Most of the stdlib, including the newest additions, is idiomatic as judged by other people but evidently not by you.


> if err is non-nil, you shouldn't even check the other return values.

Yes, it says you cannot trust functions, unless documented, to be idiomatic. Which is reasonable as not all code is idiomatic. The language goes to no lengths to enforce how the code is written in this regard (obviously). But we are talking about code that is known to be idiomatic.

> Most of the stdlib, including the newest additions, is idiomatic as judged by other people but evidently not by you.

1. I don't see how it could be. What is idiomatic emerges from writing code and seeing what works and what doesn't. Most of the stdlib was written in the early days before anyone understood what works best. And, thanks to the go1 guarantee, modifying it now is out of the question.

2. If you believe that the stdlib is idiomatic, then you have to accept that returning an int (-1) to represent an error is idiomatic. The standard library is full of that. Which violates the premise of FP-Go that (T, error) is idiomatic. That is not my claim, that is theirs.


>Yes, it says you cannot trust functions, unless documented, to be idiomatic.

That is not what it says, in fact, it almost says the opposite. It is idiomatic Go to never return useful values if error is non-nil unless explicitly documented. It never affirms your particular, personal, definition of what "Idiomatic Go" is and directly contradicts it.

>If you believe that the stdlib is idiomatic

I believe most of the stdlib is idiomatic Go, especially the newer stuff. Your excuse earlier was that the older stuff in the stdlib isn't idiomatic but the newer stuff is. But the newer stuff doesn't match your personal standards for being idiomatic either. I would challenge you to point to a large body of code that actually matches your definition of idiomatic, I do not think it exists.


> It is idiomatic Go to never return useful values if error is non-nil unless explicitly documented.

No, it is very much written from a consumer perspective. There is known, non-idiomatic, code where relying on T in its error state is problematic, so the guidance is reasonable and logical.

But we're talking about this from the producer perspective. These do not challenge each other and can exist in harmony.

> believe most of the stdlib is idiomatic Go, especially the newer stuff.

I wholeheartedly agree, at least with respect to the newer stuff, and have already said as such. Now this is where, in that newer work which is idiomatic, you point to a good example of where you can find meaningless return values when there is an error.

Clearly the vast majority of the standard library, especially in the newer stuff, does return meaningful values upon error, so we need to identify those which buck the trend if we want to hold it as a counterexample of this being an idiomatic practice.


>No, it is very much written from a consumer perspective. We're talking about from the producer perspective.

This is just a lie. It's written about Go and doesn't split its perspectives, the producer should only ever return meaningful non-error values with an error if explicitly documented, and the consumer should only ever expect those when documented.

>you point to a good example of where you can find un-meaningful return values when there is an error.

Okay, skimming the release notes for 1.21. The slog package, brand new in 1.21, contains a MarshalText method (and others, but this is the first I noticed). It returns ([]bytes, err) and it is not documented to be idiomatic as per your personal definition. Therefore, by your interpretation of the style guide and your own personal standards, it is not idiomatic. What's more, a nil/empty slice is potentially valid output from marshaling something, so the slice is entirely useless and must be ignored if err is non-nil. It is not valid to try to "derive meaning" (your terminology) from the slice being nil or not, so it is well and truly useless if err isn't nil.

Moreover, I'll assert that if Either existed in the stdlib, it would return Either[bytes[], err] instead of ([]bytes, err), and it is a limitation of Go that they're using (T, error) which does not offer the appropriate semantics.

>Clearly the vast majority of the standard library, especially in the newer stuff, does return meaningful values upon error

This, too, is simply a lie.


Anything that implements or consumes `io.Reader` or `io.Writer` would dispute that.


Yeah, there are counterexamples, but the only way to know is to read the comments or source code of the function you're calling. (T, err) doesn't convey any useful information and, in the overwhelming majority of cases, err != nil means T is a meaningless default value that should be ignored or a null pointer.

By and large I think the stuff in this repo is too much and doesn't fit Go. I don't particularly want Go to pretend to be functional, but Either and Option at least would be nice to have in the stdlib and help prevent this exact issue where there are rare exceptions to normal practices. I don't see them getting widespread use without being part of the stdlib though. If Either/Option were common in Go but io.Reader was one of the few APIs returning (T, error), that would convey a lot more information.


> means T is a meaningless default value

Go Proverb #5: Make the zero value useful.

> that should be ignored or a null pointer

nil is the zero value of a pointer, so it should be made useful per the above, but it is also inherently useful even if you put no thought into it. It allows you to know that there is an absence of a value out of the box.

And this is actually why the vast majority of (T, error) cases in idiomatic code sees T be a pointer, despite the computational and programatic downsides of using a pointer, so that nil can be returned when the value is not otherwise useful – exactly to ensure the value is as useful as possible, denoting the absence of a usable value.

If you read through idiomatic code, you'll notice that only when the underlying type is more meaningful is a pointer not used. Returning a slice is one such example. An empty set upon error is more meaningful than nil, usually. Another common instance is when 0 is meaningful, like in the aforementioned io.Reader interface. Idiomatically, one will always strive to return the most meaningful value they can.

> Either and Option at least would be nice to have in the stdlib

And if it were, then this Either wrapper in question would become useful as an overlay to it, as they would then share the same intent and meaning. But it does not match the current semantics of idiomatic Go code using the (T, error) pattern.

You can probably make it work, but code is about communicating ideas to other programmers. Either implies a dependence between variables. (T, error) has no such dependence. There is an impedance mismatch here which fails to properly communicate what is happening.


>Go Proverb #5: Make the zero value useful.

Yeah, it's a nice quip, but that's all it is. It sounds nice on first read to someone who doesn't program much. But it is inaccurate and not followed by Go, and is explicitly against the Google style guide.

The sophistry trying to paint a nil pointer as "useful" is just trying to defend a position you've dug yourself into in the process of this argument, so it doesn't really need to be addressed again.

>An empty set upon error is more meaningful than nil, usually.

This in particular is just a mistake in Go. Nil maps, unlike nil slices, cause panics, so people try to avoid ever returning them.

>But it does not match the current semantics of idiomatic Go code using the (T, error) pattern.

But it does match how (T, error) is actually used the majority of the time. The impedance mismatch is that code that currently has the semantics of Either, which is the vast majority of idiomatic Go, needs to use (T, error).


> The sophistry trying to paint a nil pointer as "useful" is just trying to defend a position you've dug yourself into in the process of this argument

You are right. I concede nil is not useful. Therefore, we agree that (T, error) cannot exist. As we see in the style guide: "Returning a nil error is the idiomatic way to signal a successful operation that could otherwise fail." This means there is no way to check the error condition. One might be tempted to write `if err != nil`, but because nil is not useful that obviously won't work. That would make nil a useful value, just as I once thought – incorrectly, as you helpfully made me realize – a nil T would be.

And as the Go style guide indicates that you cannot use the T value without first touching the error value, which is, for all practical purposes, impossible since the error value may not be useful, there is just no way this pattern can be used in any actual program.

> But it does match how (T, error) is actually used the majority of the time.

Right. As you have pointed out – of which I was reluctant to admit to, but you said it enough times that it must be true! – (T, error) cannot be used. Period. Its values do not convey the useful information required to be useable. Either does, then, indeed, match how (T, error) is used most of the time... which is to say not at all!

You mentioned something about MarshalText in the slog package showing how an idiomatic function with errors might actually be written, but then we realized that there are multiple implementations with the same name. Which one were you referring to?


>You are right. I concede nil is not useful.

You say this sarcastically, but it is actually true. A nil pointer is not useful. Once you have determined that a pointer is nil, you have confirmed that the function returning it at all was a waste of both space and time. Though it's actually only half true: nil pointers are worse than useless and they provide negative utility, because they allow invalid code to compile. A better design - which is also more efficient, even considering the overhead of tagged unions - is to not return the pointer/value at all if it would be useless. Other languages allow for this, even C does allow for it with manually tagged unions. Go is rather unique, especially among modern languages, in how it doesn't provide any mechanism for it, so people use what is available to emulate that.

For the rest of it, well, you've contorted yourself into some really interesting positions.


Exactly. (T, error) simply cannot be used in any real program. Like you say, if you try to use nil your program will crash, and error is asserted to be nil when there is no error, and you have to use that useless nil value in order to utilize T per the style guide, therefore your program will crash essentially all the time. It is impossible to write code that is valid if (T, error) is present.

It's not a question what is or isn't better. That's off-topic. It's just a question of how can we actually deal with the situation with the tools that Go gives us? MarhsalText no doubt contains the answers, but we aren't sure where to find it given the ambiguity. You went to all the trouble of looking it up to tell us about it, but now want to keep it a secret?


How does this make code easier to read and maintain? It all looks like gibberish to me.


i agree, the horror of obfuscation '__') if it have to be in FP style, this one is better

https://github.com/koss-null/FuncFrog

still prefer non-FP part tho


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.


The determined Real Programmer can write FORTRAN programs in any language.


Just use OCaml.


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.


IBM is a leader in (over)engineering.


This is giving me flash backs to RxJava. Don't force these things into languages that aren't designed around it.


I hope this remains just a curiosity that showcases a fascinatingly bad idea, rather than that anyone out there thinks that this is a good idea and start depending on it.

Picture jumping into a codebase to quickly fix something, then stumble upon ChainFirstIOK or Eithersize5 because someone went overboard showing off that they remember FP from cs classes.


The horror on my PM's voice when I showed him this library was funny. Instantly blacklisted it in my organization.


See also, fp-ts and fpdart, both of which I use for my frontend projects. On the backend, I use Rust.


Adding generics to go was a mistake


Indeed, this was the exact stuff that I loved not having to deal with in Go: architecture astronauts pushing their code golf on everyone else.

I’m quite sad to see this project as it demonstrates that Go is starting to lose many of the characteristics that attracted me to it in the first place.

(For some context, I know quite a bit about functional programming and formal type theory, having studied the latter in grad school. It is intrinsically very interesting but I believe it is a net negative in most software engineering contexts.)


"Nobody" uses this stuff in Go. I mean I'm sure it's not literally nobody, but I'm yet to encounter even one example of someone using this stuff in the wild.

I'd love to be able to survey the authors of the dozen-ish variations on this posted over the last couple of years (most of the much less elaborate than this) and see how many of them are still using it in their real code. Again I'm sure the answer isn't literally zero but I bet it's statistically-significantly fewer than all of them.


Saying no to generics sends a strong signal to FP astronauts to "take it somewhere else". This saying no to a huge number of things is the superpower of Go and its community IMO, but those "go away" signals are getting weaker over time, which increases the likelihood of needing conversations and decisions about when to use what style, which is exactly the sort of thing I enjoy not having to do with Go.

For example: this entire HN thread. And all the other libraries you mention that keep soliciting conversations, nerd sniping people who could be spending that time making better products instead of quibbling over FP code golf. But maybe those folks will always find things to quibble over...


The primary "conversations" that are spawned almost entirely consist of 1. people saying no thank you and 2. people expressing concern that this will become successful.

You can stop worrying about generics causing this.

Iterators may do a bit, but I still think that based on what is currently baking that people are going to find trying to do large amounts of work through iterators is not going to scratch their itch to do everything in a foreign paradigm.

If you want to work in a certain paradigm, then for pete's sake, do it. Go do it in a language where it's the best solution. Don't find the best solution in X, then try to jam it into Y at all costs. This isn't special to X = Haskell and Y = Go, it's true for all combinations of languages.


"It is intrinsically very interesting but I believe it is a net negative in most software engineering contexts.)"

Exactly, the place for FP was and always will be academia.

Real programs require real, readable logic.


I've ended up using at least a half dozen generic helpers in every application I've written since they were added. They've made my coding easier, more concise, and help with testing. Adding generics to Go was well-founded.


Can you share some examples? I personally haven't found any excuses to use generics yet, so I'm curious where other people are finding them useful


Here is an example in the standard library: https://pkg.go.dev/slices


I've used the slices package and I agree it's useful. I was wondering more about generics use in application code


Before the slices package you had to write those functions for all your types, it's much better now!

I also like using generics for API request/response code, ex: https://go.dev/play/p/OWf9eFmg1qF

With generics you don't need to return any/interface{} / type assert at runtime


Hmm for this example I would be more inclined to use an output parameter rather than generics

    func Request(req, resp any) error {
        // send request (http, etc)
        b, err := json.Marshal(req)
        if err != nil {
            return err
        }
        log.Printf("sent request %+v - %s", req, b)

        // read response
        if err := json.Unmarshal([]byte(`{"success": false, "error": "invalid login"}`), resp); err != nil {
            return err
        }

        return nil
    }
But I kind of agree it's nicer to use a return value rather than an output parameter. I'm excited to see what other new uses people come up with for generics!


Considering what was done with Go before generics it's hard to disagree.


This seems to be missing persistent collections, like immutable Map, Set or Vector/Sequence. Those are very important when doing FP in practice.

Is there some other established Go library that contains these collections/containers?


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.


Why?


I can think of two good reasons:

1. To look clever.

2. To make junior programmers look stupid.


For me, the best reason would be: for the heck of it. But it's IBM's repo. Someone wanted this in some official IBM capacity.


Imagine how much more productive we’d be if Kubernetes had VMMonads, and instead of yaml configs we have Kubernetes Combinators!


My eyes hurt.


The issue they link for semantic-release is hilariously baffling to me. It seems they don't support keeping a project at 0.y.z per semver because "you should just dev up to 1.0.0 faster bro" and "we know better than you bro".


cool exercise but its ultimately lubing a square peg to fit in a round hole.

A pragmatic systems programming language with garbage collection and good support for functional programming already exits. Its called Ocaml and really deserves more love.


> very important senior grug say "this too complicated and confuse to me"

https://grugbrain.dev


IBM big brain use dark FP magic for make job security


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

   y := x.map(...).native
everywhere.


`Either` works pretty well in Go. I implemented it and it felt reasonably close to Rust/Haskell (without `try!` of course).


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.


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.


You don't need support. Monad composition isn't a special feature it can be implemented directly.

Not an expert in Go but I think you can do this:

   func compose[A any, B any, C any](a func(A) (B, error), b func(B) (C, error)) func(A) (C, error) {
      return func(aInp A) (C, error) {
        res, err := a(aInp)
        if err == nil {
           return b(res)
        } else {
           return *new(C), err
        }
      }
   }
The above is equivalent to haskells fish operator >=>

The bind operator (>>=) can be implimented in terms of composition:

   func bind[A any, B any, C any](a func(A) (B, error), b func(B) (C, error), aInput A) (C, error) {
      return compose[A, B, C](a, b)(aInput)
   }


To me, the problem is that Go returns two values which makes it hard to compose functions.


That's deliberate to stop you trying to compose functions that return errors. You should be explicitly handling the errors before you compose.


great, except 99% of the time I want to handle the error by bubbling it up to a top level routine.

From my experience, "handling" the error means wrapping it in another error and returning, which is what you get from other languages for free.


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.


> You can't A(B()) if B returns two values.

You can if A is a function which takes two arguments, e.g.

  package main

  import "log"

  func A(x, y int) { log.Printf("%d, %d", x, y) }
  func B() (int, int) { return 1, 2 }
  func main() { A(B()) }
https://go.dev/play/p/Jp4B0L6NJj2


What you can't do is a(b(), c()) unless b and c return single values.

And there are no slices or channels of tuples, return values must be destructured right away.


Potentially a silly question: isn't the garbage collection going to become a problem with this style of Go implementation in large software?


Why would it be a problem? A lot of functional languages are garbage collected and it hasn’t been a problem for them


Functional languages typically have much more sophisticated garbage collectors than Go's.


It will be entertaining to see usage of this library show up in PRs on CNCF projects that IBM contributes to.


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.


They have Either, so kind of. It looks serviceable, if a little messy without language support for safely destructuring its cases.


Safe destructuring could be achieved with Church encoding.


"use the right tool for the job" /s


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.


The flying gopher logo is cool though


These abstractions are not native to go. If you miss them, pick a better language.


Just like FPP is incompatible with C, I agree. And if you want a real taste of FPP go with either Haskell, OCaml or even the good ol' LISP


Or Rust.


My contention that FP has no meaning gains another point of evidence.


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


> Rust does not have lazy evaluation

Does one need lazy evaluation to be FP?

Scheme isn't lazy to my knowledge.


I'm sure it would make a compiler's optimization job easier.


Nope


Why not use Rust instead of you need FP?


Why not use a functional programming language if you need FP?


I’m sorry, but this is just awful.

  func TraverseTuple10[F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], F8 ~func(A8) IOEither[E, T8], F9 ~func(A9) IOEither[E, T9], F10 ~func(A10) IOEither[E, T10], E, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, T1, T2, T3, T4, T5, T6, T7, T8, T9, 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]) IOEither[E, T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]



That looks pretty straightforward actually.


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


From the Go standard library

"func CompareFunc[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, cmp func(E1, E2) int) int { "

Looks like Scala


Just your average Scala function signature.

Also yes it is awful.


IBM as fuck.


It's probably valid JCL. :-)


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.


Fortunately it's all paid for by investors.




Consider applying for YC's Summer 2025 batch! Applications are open till May 13

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

Search: