120 comments

[ 3.2 ms ] story [ 190 ms ] thread
(Not directly related, but) The article reminds of a recent article named 'Why Go Getting Generics Will Not Change Idiomatic Go'[0].

Using functional programming in Go... just doesn't look right. Generics are useful, but won't change the whole Go ecosystem into FP. Maybe some uses of Map might make sense, but... idiomatic code will look the same.

[0]: http://www.jerf.org/iri/post/2955

At the end of the day, if you want to program immutably or in such a way that is declarative and introspectable, you best look to FP for prior art in those sorts of spaces. Certain problems are age-old child's play in FP-land.
of course it will change ideomatic go. there is no reason to think otherwise. just look at javascript or c++. both languages transformed heavily and nowadays people look at you strange if you write 'var' in js or use a raw pointer in c++. :-)

so Go will change and i think that sucks hardcore. there are already enough languages where folks can go crazy with types, fp, monads etc. My selling point for Go was that it was mega restricted :-)

(I'm the author of that piece.) It will change what some people insist is idiomatic Go, but my contention is that when this all settles out, call it a year after production release of the generics, the community as a whole will generally come down against "functional style" programming in Go, and people who want to program functionally in Go will discover that they continue to be quite frustrated because you need half-a-dozen more features.

It's going to be so verbose and unfun in Go that only very stubborn people are going to stick to it anyhow. I'm not saying that because I hate the style, I'm saying that because it's still going to be a lot of boilerplate wrapped around not much payload. It really won't be fun. I like "map (f1 . f2 . f3)"; the equivalent in Go even post-generics will be crazy full of boilerplate and garbage.

It will change some aspects of idiomatic Go. I don't care much at all about the "functional" aspects but I am greatly looking forward to a proliferation of more specialized data structures that are type-safe. I think one of the major weaknesses of Go in what is arguably its wheelhouse, network servers, is that network servers are often precisely where I want some obscure data structure with the performance characteristics I need to scale up some service properly. It's true that the dynamic scripting languages showed us that you can get a long way with arrays & dictionaries, but I'd like some more stuff. I've got a few places a "map" would be slightly convenient, but I've got several places where I've got architectural problems because I had to hack in an immutable data type or tree or something else that I'd much rather be using a bullet-proof, community-tested implementation of a type-safe data structure than some subset I smashed together and lightly covered with unit tests just to do this one particular job. This is the change I'm actually looking forward to. I've also got some channel pipelines that, while they are set up and working, would be easier to understand with some helper functions that could deal with them.

I am also looking forward to either using or writing a very simple concurrent pipeline that specifies a set of transform functions and the number of workers to start up for each and takes care of actually bringing up the network correctly. Or implement structured concurrency: https://vorpus.org/blog/notes-on-structured-concurrency-or-g... Go may never enforce this but I'm tempted to try to enforce it in my programs.

thanks foe the reply and i understand your sentiment. i think what will happen is that people will start advocating for more and more syntax changes. think of => in js instead of function. a minor tweak but with HUGE impact. probably much more than const/let etc. and eventually more and more of this will change the language.

but i hope i am wrong. :-)

in general i get your point amd agree with it. i have written large go codebases and ran into exactly the same issues. but i wouldn't agree to the trade off :-) then i would have used Rust in the first place I guess

"i think what will happen is that people will start advocating for more and more syntax changes. think of => in js instead of function."

They won't need to start; hit the Go issue tracker to see a whole bunch of them have already been made and rejected. People have been advocating for them for a while. I virtually guarantee that of all languages, the Go team will not change their mind in response to existence of generics for them. Their bar for accepting changes is very, very high. It is not insurmountable, but it is very high.

Remember, Go didn't come out yesterday. Go is now over ten years old. We have a good idea of the velocity of changes it makes. It is low.

Java is still an excellent option in many respects.

Notably it has: - mature generics - streams - exceptions - rock solid for server apps - some FP style libraries - good timing - probably the widest universe of libraries

Only downsides are, 1) JVM startup times and 2) it's not the newest shiny thing.

I looked at the whole language comparison recently for a greenfields project. While initially optimistic, I was surprised how many capabilities some new alternatives are missing, and by the enthusiasm to write thousands of lines of low value C-style error checking.

An architectural assessment: Above a trivial level of software, the majority of code composes something that can fail. Exceptions are an ideal mechanism for this general truth.

I have thought before that if Java's interfaces worked like Go's did, with implicit satisfaction instead of having to declare explicitly, that there would probably be no Go. If I could go back and change one thing about Java, that's what it would be. I know there's other more obvious pain points that most people would pick, but I think this is a subtle one that undergirds several of the more obvious ones.

(To be clear, I am aware that Go and Java still have differences beyond that; my claim is that with this change Java would have been "good enough" that Go either never would have been spec'd or it never would have gotten any traction if it was. Java, however, has left a significant window open for Go because it has gained traction.)

The problem with the way Java does it is that it forces a temporal dependency between interface and implementation, all to avoid a programmer error which on average will happen less than once per programmer career: http://www.jerf.org/iri/post/2954 This causes a lot of gyrations to be necessary to use them properly, which starts introducing the desire for heavyweight ways of autogenerating this, etc. and roll around that cycle a few times and that's how you end up with as much XML as code in your codebase.

I think Java and Go are a really interesting case study for people interested in programming languages, because on paper they are virtually identical languages (especially if you ignore channels and select in Go), and it gives a clean demonstration of how even those not-very-large differences end up having a profound effect on how solving problems in those languages are best done.

I mean the GC pause times and static binaries are huge to me as well vs java. go is just a better languages to develop with in my mind. I have to spend less time optimizing and more time getting shit done.
Functional programming is a kind of leetcode thing that I don't want to see come to Go. In many cases is destroys readability, and allows people to forget about the complexity of the code they are writing (in a bad way).

With most Go code today, you can just look at the indentation, or count the for loops.

https://github.com/golang/go/issues/45955#issuecomment-83235...

It's a shame that someone can become so misguided about functional programming. Functional programming isn't about destroying readability. It's actually the opposite; it enhances the ability to reason about code by (a) being disciplined about where side effects are allowed to happen, and (b) leveraging reusable abstractions for common patterns (e.g., transforming each element of a list, building a generic container that can be used for arbitrary types without sacrificing type safety, etc.) rather than re-inventing them every time with copying and pasting. Generally, functional programs are safer and shorter than their procedural/OOP alternatives, which is a good thing. Less code means fewer places for bugs to hide.
In this article, which code sample would you say is more readable? The imperative style or the functional style?
Theory vs practice for the specific language. Here, the extra signatures and unnatural patterns destroy the readability.

It could still be better though if you have, for example a number of pipelines for your data. There, the reuse of Sort/Group/Frobnicate/... in close by blocks would actually be nicer.

But yeah - the more extra syntax is required for those pattern, the worse they are in practice. (or, the more things you have to abstract in a small range to make it useful)

I would say the imperative version is easier to read in this article. The functional version doesn't really illustrate the benefits of functional programming very well, IMHO. I think there are two issues that reduce the effectiveness of the example:

1. Both examples contain a bunch of extra logic that has nothing to do with the difference between the two styles. For example, the code that converts users to that `UserLevelPoints` struct takes up a lot of space in both examples and is essentially the same in both. I think it would have been better (for pedagogical purposes) to just return the users (or perhaps a simpler struct containing a level and a user).

2. Both examples still have all the verbosity that is typical of imperative code, since it's still Go after all.

If one were to write the same function in a syntax which was designed for functional programming, the result might look something like this:

    topUser = head . sortBy points
    topUserPerLevel = topUser . values . groupBy level
I find this vastly quicker to read and understand than either of the examples in the article. But that relies on me knowing the functional-optimized syntax (e.g., that the `.` operator is just function composition), and for most imperative programmers that is the main hurdle. Since I am familiar with both styles, I can tell you that this would take me about 6 seconds to understand, whereas understanding each of the examples in the article took me at least a minute to even read the code, let alone understand it—and if there were bugs I wouldn't have noticed.

In contrast, with the simple functional version I provided, I can read it in seconds and be reasonably confident that it is correct (assuming all the pieces fit together in a valid way, which a type checker can check for me).

If the unfamiliar syntax prevents you from grokking the example I provided, it's reasonable to assume it's just some clever code golf that should be discouraged in production code. But please resist that temptation; this is actually what good code looks like. It's simple, not repetitive, and there aren't many places for bugs to hide.

One person's readable code is another person's unreadable mess. It depends on what kind of program you're writing.

The fact that functional programming emphasizes "abstraction" so much means that, by definition, you're further away from what's actually happening. So looking at some declarative code it's not immediately obvious what the computer will actually do. The kinds of programs I write require me to care about that deeply. So when I see declarative code all I see is a giant question mark as I wonder what in the world the computer will actually be doing when it executes that code.

Anyway, my point isn't that one is better than the other, it's that it depends on what you're doing. For some of us, the higher levels of abstraction are actually less readable.

I think this is a reasonable point, and of course you could take it further and say that assembly language is better than functional programming when you're doing work that requires you to reason about what the CPU is doing exactly.

But I would argue that the majority of Go programs should not require the programmer to follow the intimate details of a program's execution when trying to understand the business logic. The fact that the language has a garbage collector would suggest that it was designed for programs where some details can be hidden away.

So I agree with your general point, but I am somewhat skeptical about its application to Go.

If the lack of abstraction is a requirement, why use a language like Go at all? Shouldn't you be using C for these sort of projects?
zig is also a really good contender in this space as well. it also maps fairly cleanly to assembly while providing a bit more safety.
The only thing that makes programming any useful program even remotely possible is abstractions. The human brain simply breaks down even at the fraction of the complexity of any non-trivial app. There is wrong abstraction, and over-abstraction that can obscure a codebase, but it is orthogonal. And no, you can’t write simpler code, there is essential complexity, that can’t be reduced.

Also, it’s kind of laughable to think that writing code in a high level language with a runtime, GC, etc, you have any idea what will happen at execution.

I'm not saying to never use abstractions. There's a balance.
All true, but the dichotomy here isn't between functional and imperative - it's between imperative, and a mix of both.
> it enhances the ability to reason about code

In your opinion.

> Generally, functional programs are safer and shorter than their procedural/OOP alternatives

Please provide any evidence that this is true, especially the ‘safe’ part.

Any study I’ve read says there’s almost no effect on language choice at all on bug rate.

> > it enhances the ability to reason about code

> In your opinion.

Correct. But my opinion is informed by years of experience with functional programming, procedural programming, and OOP (each).

> Please provide any evidence that this is true, especially the ‘safe’ part. Any study I’ve read says there’s almost no effect on language choice at all on bug rate.

Sure, here is a paper which provides the evidence you're looking for: "A Large Scale Study of Programming Languages and Code Quality in Github" (https://dl.acm.org/doi/10.1145/2635868.2635922). That paper concludes:

> The data indicates functional languages are better than procedural languages; it suggests that strong typing is better than weak typing; that static typing is better than dynamic; and that managed memory usage is better than unmanaged.

I personally don't trust academic studies on programming language effectiveness, since they often contradict each other (e.g., https://arxiv.org/abs/1901.10220 contradicts the paper I cited above). But if you're looking for a peer-reviewed paper, there you go.

Choosing the right paradigm absolutely has an effect on the bug rate. If you have to write more code, repeat code, or deal with low-level details irrelevant to the problem at hand, you are going to make more mistakes. On top of that, functional languages often have better type systems than procedural/OOP ones, which helps with bug catching that much more. Taking this to extreme, with dependent type systems you can actually verify arbitrary mathematical properties of your programs.

How much experience do you have with functional programming? I have professional and academic experience with both functional programming and OOP, and everyone I've met with that experience agrees about the trade-offs I've been discussing in my comments.

A leetcode thing? Most of leetcode solutions I've seen are terse and over optimised for a specific problem, don't think I've seen any functional programming when I was doing them.
i think what he meant was not leetcode the service, but leetcode as in elite code
While I agree that Golang might not be the best language to start functional programming in, in my opinion FP is less about leetcode and more about proper abstractions and reasoning.

Yes, functional programming exposes you to a lot more concepts that just conditional jumps, but that's fine. Because while a conditional jump might be a simple concept, it's mighty powerful and maybe not constrained enough.

Functional programming gives you more and simpler building blocks to put together. Yes, they have fancy names but most of the time they are almost trivial ("set together with an associative binary operation"). These building blocks interact with each other in very well defined ways that you do not get when trying to compose software in an imperative way.

I seem to be a bit of an outlier, but I am not looking forward to generics in Go. I don't like the kinds of discussions it attracts...vague hand-wavy arguments about "expressiveness" and "purity". Expressiveness to me means more arguing in code reviews, and nothing is stopping us from writing pure functions now, I do it all the time.

I maintain a few large Go codebases, tens of thousands of LoC each, and not having generics in practice is way, way down on my list of challenges. Equivalent Java codebases that I've maintained are usually harder to maintain because of generics, with over-eager functional programmers itching to show off their skills in abstraction, instead of solving business problems.

I hope I'm wrong, and that this will make Go a better language. But I doubt it.

I'm hoping people won't go crazy with it. Generics are one of the things that drive me nuts about the Rust ecosystem... it feels like every library in Cargo is based around a bunch of types with a bunch of unnecessary type parameters. Like, if I want to parse command-line arguments.

What I do want is to stop implementing sort.Interface over and over again.

Agree, this is my hope. Seeing the same thing play out in other language ecosystems though, I'm just not very optimistic about it.
> Like, if I want to parse command-line arguments.

What command-line parsing libraries have “a bunch of unnecessary type parameters”?

Only library I can think of which even has them would be structopt, and that’s not unnecessary, the entire point of the library is to deserialise to a structured type.

And of course rust was designed from the ground up to leverage generics, e.g. by design it does not have reflection / RTTI, interactions with variable or foreign types are thus generics based (often, sometimes trait objects will do though they have their own tradeoffs), or it doesn’t have overloading so while not handing all overloading use cases by a long shot it uses conversion traits (aka generics) for “convenience” overloads e.g. foo(string) and foo(path) become foo<T>(_: T) where T: AsRef<Path>.

Yes, Rust was designed from the ground-up to leverage generics, and as a result, the ecosystem is full of generics. There is overuse of generics in Rust.

> ...by design it does not have reflection / RTTI,..

I don't think of those as comparable features.

Reflection is more or less an alternative way to accomplish many of the same things that you get with macros in Rust. Where you'd see reflection in C# or Go is for something like serializing an object to JSON, and in Rust you'd get some library with a macro to do it for you. The Rust version could still be completely monomorphic, most of the time.

RTTI is just std::any. Rust has it. However, 99% of the time, when I'm using dynamic_cast<T>(x) in C++ or switch x := x.(type) in Go, I would have been using an enum in Rust.

The convenience overloads for Rust are nice, that's not the overuse I'm talking about. I'm talking about the fact that some library authors seem to avoid &dyn like it were poisonous, even if it makes total sense and would make large chunks of your library monomorphic. There are also minor code size issues that come about because some library authors forget that the convenience overload should probably just call an underlying monomorphic function, especially for cases like AsRef arguments.

I am under the assumption here that if you really like generics, you probably like Rust, and vice versa. This is kind of a taste thing, so what is overuse of generics for me (hello, C++'s std::chrono, std::mt19937) might be the bee's knees for you.

I did a fair bit of Haskell and C++ before Rust, and as I got more experience with those languages, I used generics less. Monomorphic types are always easier to understand, and they should be preferred.

> There is overuse of generics in Rust.

You made that claim several time, but still provided literally no evidence, just a lot of handwaving and careful avoidance of any sort of concrete point.

> I don't think of those as comparable features.

> Reflection is more or less an alternative way to accomplish many of the same things that you get with macros in Rust.

They go together e.g. where Go uses reflection, Rust uses Derive or manually implemented traits and generic functions working with those traits. JSON is a pretty obvious example there.

> I'm talking about the fact that some library authors seem to avoid &dyn like it were poisonous, even if it makes total sense and would make large chunks of your library monomorphic.

I would not necessarily disagree with that although I would word it more kindly: since generics always work, they're the default, as a result dynamic dispatch is a fallback when either you must have dynamic dispatch or someone pointed out that the static dispatch was less than optimal (which can be impossible to find out through microbenchmarks).

Plus `&dyn` means you're now restricted to borrow-ability, and `Box<dyn>` incurs allocation costs. Generic function skip both concerns.

> You made that claim several time, but still provided literally no evidence, just a lot of handwaving and careful avoidance of any sort of concrete point.

I'm not really interested in arguing that point, just because you asked me to give concrete details doesn't obligate me to provide them or engage in the conversation to a level of detail that would satisfy your request. It's okay to share an opinion without defending it.

Part of language ergonomics is that languages provide you with with "escape hatches" to avoid making certain decisions in the design of your system. This might mean that the decisions are made by the callers. Good API design requires figuring out which questions should be answered by your API and which questions should be answered by the caller.

Just as an observation... generics are an escape hatch that let you avoid choosing concrete types, and forces your caller to do that for you, and in general, I think library authors use that escape hatch too often. This is not specific to Rust.

The vast majority of the usage of generics in Rust are as replacements for inheritance or dynamic dispatch. It's not very different structurally.
As someone who's working on code base at ~100k LoC I'd say that generics would be great to have:

- We have some utilities (e.g. caching layer) which now either needs to use interface{} (and lose all type safety) or hard-code the type their dealing with (and thus be duplicated).

- Lack of iterators are annoying. We often need to stream data from a database and now we need to introduce a separate iterator-interface for each thing that can be streamed (`Next() (Entry, error)`). Working with these iterators are clunky since we can't write common tooling around it (e.g. "fetch the first entry").

I've written quite a few containers that use interface{} like that...they don't really "lose all type safety", you write a typed container around it (usually a one line implementation) that does the type assertion in one spot, and that's it, you never deal with interface{} again. For all practical purposes, it's just as type safe.

I agree that lack of iterators is one area where there is permanent annoyance. Personally, I think that annoyance is a lot less than the other problems that come with generics (at least in other languages).

I'm not saying there aren't legitimate scenarios where generics simplify things. I'm completely convinced that there are. But holistically I don't think they outweigh all the other baggage that they come with.

(comment deleted)
I've often wondered about this. Does this technique (a type safe wrapper around an interface{}ing class) kind of accomplish the same thing that generics do for Java? It would result in the same kind of type-erasure in the end, I'd assume.

In a weird way, with this technique, did Go have "manual generics" all along?

Except you still have to write out the typed implementation - even if it is small. In Java this is not necessary.
Not really, but aside from the functional aspect that tends to come with them, all generics are really saying is "this operation will performed on a type to be named at runtime", optionally with a constraint that says the type-to-be-named-later will adhere to a certain interface (contract).
On a type to be named _separately_.

To-be-named-at-runtime is simply dynamic typing. It's polymorphism without static typing.

Generics based on type erasure, e.g. Java, wrap that with a type system hoping* to prove safety at compile time, yet compile to a single dynamicly typed implementation, that does not use this type info at runtime.

Generics based on code generation / specialization, e.g. C++, generate distinct run-time implementations with the type hard-coded. (Though you can plug in dynamic typing by making the hard-coded type a pointer.) In C++ this is more than optimization, it's semantics — you can specify distinct behavior for specific types.

JIT compilers can start from a single dynamic-dispatch implementation and generate specialized implemetations for some types, bridging the divide.

But in any case, the common meaning of "generics" is not merely polymorphism, it's polymorphism plus a type system where you _name_ the type you're gonna use.

* https://3fx.ch/typing-is-hard.html#java

> I've written quite a few containers that use interface{} like that...they don't really "lose all type safety", you write a typed container around it (usually a one line implementation) that does the type assertion in one spot, and that's it, you never deal with interface{} again. For all practical purposes, it's just as type safe.

At that point aren't you just writing out the generics by hand? Why would you not want a built in language feature for that which saves you the trouble while simultaneously ensuring the code is correct.

(comment deleted)
You're writing out the concretization by hand. Generics as a language feature are much more complicated than their concretizations, which is my point. Generics are to their concretizations as "-e^(iπ)" is to "1".
If you're writing generic code using interface{} and then wrapping that in concrete wrappers then you're actually doing both!
(comment deleted)
I’m talking about formal generics, where there are type system mechanisms for specifying type parameters and constraints. I suppose there are really three things we’re considering here: the generic implementation that operates on some arbitrary type, the concretization that has only concrete types, and a generic type specification which is only required if the compiler emits the concretization instead of the programmer. (I'm ignoring interfaces as they aren't really relevant here, that I can see. And hopefully I'm using the right terminology here.)

What I’m arguing is that for most cases, the implementation and concretization are so easy to write by hand, are safe enough, and perform well enough, that I don’t think the downsides of having the compiler emit the concretizations are worth it. The downsides being the new complex language primitives that have lots of consequences that trickle out to the compiler, runtime, and ecosystem.

> a generic type specification which is only required if the compiler emits the concretization instead of the programmer. (I'm ignoring interfaces as they aren't really relevant here, that I can see. And hopefully I'm using the right terminology here.)

Unless I'm mistaken, the "generic type specifications" are just interfaces. Instead of specifying the concrete type of a function parameter, you specify which interface it needs to implement, and then everything else is the same (except you may need to define getters/setters if you need access to struct fields). There's no need to have a new language primitive. That's certainly how Rust does it anyway. And given that Go's interfaces are structurally typed, it'll be even simpler in Go.

I don't really understand the argument that generics are complex. As far as I can see, they're super simple.

That's just going back to dynamic typing.
It's not anywhere close to that. There's a tiny expression where we override the type system, but everything else in the program is still statically typed, and we get to avoid all the complexity of generics.
I have similar feelings. I’ve been coding a long time, and the longer I do it, the less sure I am about any of this stuff. The one thing I can say for sure, I find it relatively easy to jump into an arbitrary go code base and make sense of it. I can’t say this about languages with all the features people are clamoring for, and I’ve worked on large projects in Ruby, python, Java, scala, clojure, swift, JavaScript, typescript, and a few others.

I’m not sure exactly which qualities/features of Go make it so much easier to read, but I worry that change will lead to the Go losing what makes it unique and special.

Side note: clojure was oddly the second easiest for me to read.

It is no surprise that Clojure was the easiest to read. It is composed of S expressions and some syntactic sugar.
Making sense of a small, detailed part of a codebase is meaningless. But too much detail will make it much harder to see the big picture, something that may be more apparent in more expressive languages.

On a deliberate hyperbole, reading assembly, each line is trivial, but it is very very hard to make sense of what does it do compared to a line of a high level language.

> I’m not sure exactly which qualities/features of Go make it so much easier to read,

In my experience, golang is harder to read. It's overly verbose that it's hard to figure out the underlying logic. What can be written as

    items.filter(|n| n.price >= 10)
         .groupBy(|n| n.source)
         .mapValues(|v| v.sum()/v.length())
would be over a dozen lines of code in golang, with potentially one off functions everywhere. This only gets more obnoxious with larger code bases.

The problem is that even if golang added generics, they won't be composable. In their proposal, they proposed adding map/filter/etc functions in a separate package, not as functions on slices as a proper language would do. The same mistake they did with not having any functions on strings, but kept them in a separate package.

Also, they need to improve their anonymous function syntax.

    foo("param1", func(a int, b string) bool { return len(b) < a }
is verbose, more advanced languages simply shorten it to

    foo("param1", |a, b| b.length() < a)
or something equivalent
(comment deleted)
There was a thought expressed in one of the original drafts that went along the lines of "the only people worrying about generics will be library authors", which I think will and should be true. The structure and architecture of existing codebases won't change, but the removal of casting to and from interface{} when using libraries will be a welcome change.
> [...] and nothing is stopping us from writing pure functions now, I do it all the time.

One point of generics is that you have to write your pure functions less often..

> I don't like the kinds of discussions it attracts...vague hand-wavy arguments about "expressiveness" and "purity".

Pretty much every argument about how to write Go programs is hand-wavy.

I'd actually argue (from years of experience at least) that FP discussions of code are pretty much the only ones of actual substance instead of mostly aesthetics and opinion and heuristic.

The best part of writing Haskell professionally is I no longer have to deal with hand-wavy programming folk-isms. We can do better, and it's not that hard.

Any stylistic discussions are like that, I agree. The opinions that Go makes are themselves hand-wavy, like any stylistic opinion.

What's important to me is that the decision is already made, and when me and my team are writing code, we don't have to worry about making those decisions over and over again. They're already made for us. IME the costs/benefits of the different styles are not even worth the discussion, just pick something and move on. This is the aesthetic that I like about Go.

Exactly. Is it RT? Is it well-typed? Is it as simple as it could be? Is there a small number of inhabitants? Ship it. Using some weird recursion scheme when fold right will do?
All languages are converging to the ultimate language: The TypePyGon!

Jokes aside, I wish our tools were more "final". When is a programming language done regarding the pain it was meant to solve?

> When is a programming language done regarding the pain it was meant to solve?

Elixir is often presented as "done" (https://elixirforum.com/t/is-elixir-done/20830/12). For example, the depreciation policy mention that function can only be removed on a major version (1.X to 2.Y for example), and for now it's still in 1.X and no Elixir 2 is planned.

It still has releases (https://elixir-lang.org/blog/2021/05/19/elixir-v1-12-0-relea...) to follow Erlang releases, but Erlang itself is also more conservative than some other languages.

> When is a programming language done regarding the pain it was meant to solve?

It's hard to imagine that a language can be "done" when the problems being solved with it keep changing and we keep discovering new techniques for solving them.

When is automotive design done? Farming?

Then why not just use C++ since it’s already accumulated all the features?
The absence of features is often one of the greatest values a tool can have.

Features that you don't use carry a cognitive load, increase the number of choices you have to make, and reduce the usability of other features that they have to be disambiguated with.

maybe go culture and community will add its own pragmatic flavour to generics use ? not everything has to end up in endless categorical soup
I hit the 100kloc size project in Go, the lack of generics and the never ending nested if err != nil lines were a huge part of that. It wore on myself and my team, and was certainly not helpful for the rsi I was experiencing.

10kloc is pretty small these days realistically. Especially in Go land so I get it. But yeah from my experience that simplicity at some point becomes more a burden and less a help.

It depends on the type of the work you do. Whenever I have to cast ints to floats because I want to use something in math library, I want to claw my eyes out.
Most problems that programmers are trying to "solve" seem rather banal to me. They aren't inherently difficult in a way that generics really does much to help. Honestly I think abstraction is often the route that smart - or wanting to be perceived as smart - programmers take to keep themselves entertained. Ultimately it is overkill and wastes time in my opinion.
Generics can and will be abused for terrible stuff, that's a reality. There are however a bunch of spots where the difference in not having them is a lot of additional manual code, or unoptimized code using reflection.

Every language feature can be abused, that's why Go tries to have a small amount

I think people have a habit of taking relatively surface-level features of functional programming and focusing on them to the exclusion of the real benefits of functional programming. The use of something like compose3 in the "functional" example here is a perfect example. Sure, function composition is a "functional" thing, but I don't see why you wouldn't instead write something like (handwaving wildly on the specifics, since I know barely any Go and certainly am not up to speed on the generics proposal):

    func getTopUsers(posts []Post) []UserLevelPoints {
            return posts.GroupBy(func (v Post) string { return v.Level })
                        .Values()
                        .Map(getTopUser)
    }
This pipeline style is significantly easier to read (especially without having to put all those extraneous type parameters in your call to compose3!) and doesn't actually lose any of the core advantages of the functional style: purity, testability in isolation, equational reasoning, and so forth. Sure, the Haskell equivalent might use composition… but composition reads more or less naturally in Haskell, and I don't think it does at all here in Go. If your specific approach to "functional programming" makes your code theoretically easier to reason about but practically harder to both read and write, then is it really helping you much?
Exactly my thought: why is this clearer?

I'm often think FP-first (data science) -- here though, I was surprised by how I went back over the imperative alternative.

Avoiding `append()` isnt worth it in a language where this level of annotation is required to do FP. It's less clear.

Piling on here - My impression of your snippet here is that GroupBy() and Values() both construct (potentially very large) temporary arrays on the stack.

Maybe Haskell and F# can elide this kind of thing, but it's not something the Go compiler is going to do.

Rust and OCaml (the latter if you use the right libraries) can definitely avoid it. Why couldn't Go?
I've been waiting to do things like that in Go for a long time. It can be done efficiently just like the Java Stream interface.
An alternate-universe Go with a lot more (& slower) compiler passes could do it, but I don't believe there are any plans to build such a thing. As it stands, this kind of code (where all intermediates have type []T) will run much more poorly than you could do with imperative loops.

The final generics design is probably not expressive enough to do it ergonomically in user code neither (building something where the intermediates are a library type with a last `.something()` call to collect a final []T).

OCaml compiles as fast Go.

No secret sauce, just having the pleasure of chosing multiple backends, including an interpreter.

Bytecode runtime during workflow, optimized native AOT for release and final production tests.

That's just bad design - iterators and sequences need to be lazy. The intermediary type should not eagerly create large arrays on the stack, it should only ever do real work once the iterator has been driven.
No, they don’t need to be. They can be. Go wasn’t designed as a functional language but to be fast to compile, fast to execute, zero dependency binary and simple syntax. It achieved that by a mile.
Implementing iterator combinators by eagerly evaluating them into temporaries is a bad, naïve design that results in the issue illustrated above.

A much better design is to do it lazily which is possible if you can store closures as fields of a data structure.

Not sure what fast compilation and execution or dependencies has to do with this. Implementing iterators that way is the worst possible solution that results in slow execution that can blow up the stack and crash a program. It would be better not to have iterators at all than to require them to be eagerly evaluated.

You can support functional features without being a Haskell...

Yes, lots of science. Go has slices which require specifying a size and you iterate over them using a for statement. You want lazy, pop in a channel and do it lazy.
This isn't "lots of science" it's rudimentary software engineering...

Sending data through channels just to implement an iterator seems insane. It's not strictly lazy either...

Why do go developers reject simple patterns that make faster code?

How are those magical lazy operations implemented in other languages? Someone had to take that insane step. It’s just not by default in go.

Look, I’ve written lots of scala and erlang so I get your argument. It just does not exist in go. And that’s okay.

Never got caught thinking “gee, need a lazy collection”. Maybe a stream when reading stuff from the ether. In go, channel was enough. 5 minute job. Nothing insane.

They're implemented with closures, not passing things through FIFOs. It's not magic. You barely need a compiler pass to implement the sugar for `for` loops: it's a trivial transformation to a `while`.
Okay, but what’s the benefit for go. I can iterate over slices, maps and channels (for ... range). Slices must have a capacity defined. What’s the benefit of lazy iterator?

for over a channel is in essence lazy. Channel stores the data as a linked list. So what’s the benefit.

The benefit of generic iterators or implementing iterators lazily? The benefit of generic iterators is that they make SOLID programs easy to write, especially when converting data. It makes any kind of data ingest or hand rolled parsing/semantic analysis easy to write, read, and hard to screw up - which is 90% of business logic.

Lazy evaluation is the only reasonable way to implement iterators so you don't have to pay the costs of intermediate data structures or sending data over channels. Insertion into a data structure isn't free. Function composition is.

That’s probably okay if the code is I/O bound anyway, but in Go it will run a lot slower than doing multiple steps within a single for loop.

For loops may be unfashionable but they aren’t hard to read.

I don't find iterator combinations hard to read. Especially with postfix methods.

I do find deeply nested for loops with outer variables that might start uninitialized or need to be mutable much more difficult to read.

Sometimes moving an inner for loop to a separate function (that will likely be inlined) is better to explicitly show its inputs and outputs.
I agree they need to be lazy. This is why it's problematic - Go is only adding generics and not laziness.
The standard Ocaml compiler doesn't elide it and as far as I'm aware neither do any of the publicly available libraries. All of the lazy stream implementations in Ocaml have a performance cost similar to Java streams. Haskell and Rust are relatively unique in that they can completely eliminate the performance cost in the typical cases.
(comment deleted)
FWIW C# would as well (LINQ)
(comment deleted)
Yeah if you're writing "Compose3" you've already lost. The extra type annotations only make it worse.

    x := foo()
    y := bar(x)
    z := baz(y)
Wow.
Ok, now what if foo, bar and bar are async? Nullable? Result types?
Well it's not like Go has do notation so I don't know what you're expecting here.
I’m pointing out that functional programming goes beyond trivial things like compose3 and list operations (useful though they can be).
Sure, but e.g. monadic futures aren't going to be very usable in a language without do notation, custom operators, or non-verbose lambdas. Might as well just use channels. As for nullability and error handling, writing "if" over and over again might be bad, but in Go I don't think functional approaches are going to be any better. Maybe liftA2, etc. on pointers would be usable, but not much else.
i really love fp. heck i was even overly zealous about it few years ago, annoying everyone around me with it :-)

but this development kibda makes me sad :-) i use go for so many projects because it finally restrained me and my employees enough to not argue about language features all the same. and all our code bases look the same. i loved that about go. no, with generics, and all potential libs (just wait for go lowdash, go rambda, go fantasyland, go promise etc), that will come to an end :-)

Missed a big opportunity to go with godash.
I dont understand the list of supported features. In my opinion currying and variadic functions are supported in go to some degree.

Variadic functions even links to the go example, but classifies them as unsupported in go.

Variadic functions aren't even necessary for functional programming in the first place.
Go does have variadic functions, but what are you counting as "currying support"?
Well, since functions are first class citizens nothing stops me from turning a (a,b,c) into a (a)(b)(c)
Sure, but nothing helps it either, and you're going to take a decent performance hit if you do that because you're turning one function call, possibly inlineable, into three that aren't going to inline, even with new Go generics you will not have enough power to write a "ToCurried" function that converts to a curried version at compile time (I think you could theoretically do it with reflect at runtime, but now you're taking an even bigger performance hit...)

I would not normally say any language "support currying" just by having first-class functions. Python can do several of those things I said Go can't do and I still wouldn't call that "supporting currying". It sure isn't anything like the experience of using curry & uncurry in Haskell: https://hackage.haskell.org/package/base-4.15.0.0/docs/Prelu...

Thanks, looking at it this way makes more sense!
Go seems like one of the worst languages you could choose for functional programming. Why paddle upstream when you could either write idiomatic Go or switch to a language properly designed for FP?
Some people are stuck with Go for their current projects, perhaps?
Mixing in generics to an already existing code base sounds like a bad idea.
Anyone that has DevOps on their daily tasks is now bound to deal with Go code, due its relation to Docker, Kubernetes and cloud infrastructure, just like we need to deal with C on UNIX clones or JavaScript on the browser.
What relation does golang have with docker for devops use? I'm also wondering the same about k8s, other than the go templating language used by helm charts. And if it's the development of docker itself which is written in go, I wouldn't consider that devops (same as I wouldn't say editing images in photoshop is C++ development).

I'm curious, because I've used k8s and docker extensively for devops, and aside from templating, which is arguably not so much golang as, well, just a templating engine. And I've thankfully not have had to use golang for anything serious.

Plugins and other software that one might feel like customising.

https://landscape.cncf.io/

As always, using the platform language is the best road to avoid additional development costs dealing with interoperability and tier 2 compatibility issues.

Although some stuff is now happening in Rust as well,

https://deislabs.io/

I see. I think what confused me was the statement "Anyone that has DevOps on their daily tasks is now bound to deal with Go code", since I've not found it to be true. And, I've yet to find a single need to customize any plugin/tool. The bar to do something like that is also exceedingly high, as the added maintenance cost is immense, since you now have to build and maintain your changes towards a living project. So, I think we might just have different interpretation of what DevOps entails.

In addition, even if writing infrastructure plugins were to considered necessary as part of DevOps (though I consider this a stretch), writing golang is again not a requirement. The CNFC isn't exclusively filled with golang plugins either. I'd argue most are something else C/C++/Java/web-langs though I haven't made the effort to check. I'd venture a guess at one in four being golang, at most.

I agree however that language evangelism benefits no one, and especially agree on the sentiment of using the platform language to avoid additional development cost. That said, and this is probably a bit more subjective, this does not follow from/to the original statement I took issue with.

Whenever I see interface{} mentioned in Go I see vague references to polymorphism.

That is wrong.

Interface is a special type in Go. All interfaces in go, including ones you declare, hold 2 pieces of data in memory

1. Pointer to the concrete type

2. Metadata about the concrete type

interface{} is not a special thing, it is just the empty interface, and everything satisfies the empty interface.

This is quite neat. All a type assertion does is follow the pointer and check the type data.

There is also a type switch in Go. https://play.golang.org/p/22aegLocHXI

This makes building handlers around the dynamic type easy. But for the most part: Single function interfaces. Compose those to make make larger interfaces if absolutely needed (see io.Writer, io.Reader and io.ReadWriter)

I am ambivalent over generics. I can today write:

type meh interface{ foo() error }

type whatever struct {}

func (h *whatever) foo() error

And still pass []whatever{} to func canihaz([]meh) so not sure how much generics really add to that.

I don’t mind generics but when they are there, I use them. Whatever :)

Accepting interface{} is a code smell. I’d accept that only when dealing with wire data. Never in the business logic.

This is a pretty strong case for not adding generics, and I say this as someone who doesn't particularly enjoy the range loops splattered everywhere.

What was pretty straight-forward code, just a bit verbose, becomes a tangle of library functions being composed. It's not shorter, it's not easier to understand, adds dependencies, and most likely will not perform any better. There is nothing gained.

Why choose Go as your programming language if you want to code in a functional style?