[HN Gopher] Functional Programming in Go with Generics
       ___________________________________________________________________
        
       Functional Programming in Go with Generics
        
       Author : ngaut
       Score  : 103 points
       Date   : 2021-05-27 01:58 UTC (21 hours ago)
        
 (HTM) web link (ani.dev)
 (TXT) w3m dump (ani.dev)
        
       | pharmakom wrote:
       | 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?
        
         | pjmlp wrote:
         | 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.
        
         | eru wrote:
         | Some people are stuck with Go for their current projects,
         | perhaps?
        
           | minkzilla wrote:
           | Mixing in generics to an already existing code base sounds
           | like a bad idea.
        
       | andolanra wrote:
       | 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?
        
         | mjburgess wrote:
         | 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_.
        
         | [deleted]
        
         | platinumrad wrote:
         | 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.
        
           | pharmakom wrote:
           | Ok, now what if foo, bar and bar are async? Nullable? Result
           | types?
        
             | platinumrad wrote:
             | Well it's not like Go has do notation so I don't know what
             | you're expecting here.
        
               | pharmakom wrote:
               | I'm pointing out that functional programming goes beyond
               | trivial things like compose3 and list operations (useful
               | though they can be).
        
               | platinumrad wrote:
               | 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.
        
         | mappu wrote:
         | 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.
        
           | [deleted]
        
           | Yoric wrote:
           | Rust and OCaml (the latter if you use the right libraries)
           | can definitely avoid it. Why couldn't Go?
        
             | mappu wrote:
             | 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).
        
               | pjmlp wrote:
               | 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.
        
               | duped wrote:
               | 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.
        
               | rad_gruchalski wrote:
               | 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.
        
               | duped wrote:
               | Implementing iterator combinators by eagerly evaluating
               | them into temporaries is a bad, naive 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...
        
               | rad_gruchalski wrote:
               | 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.
        
               | duped wrote:
               | 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?
        
               | rad_gruchalski wrote:
               | 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.
        
               | duped wrote:
               | 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`.
        
               | rad_gruchalski wrote:
               | 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.
        
               | skybrian wrote:
               | 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.
        
               | duped wrote:
               | 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.
        
               | skybrian wrote:
               | Sometimes moving an inner for loop to a separate function
               | (that will likely be inlined) is better to explicitly
               | show its inputs and outputs.
        
             | wrnr wrote:
             | 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.
        
             | skjfdoslifjeifj wrote:
             | 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.
        
           | Serow225 wrote:
           | FWIW C# would as well (LINQ)
        
       | mpfundstein wrote:
       | 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
       | :-)
        
         | graftak wrote:
         | Missed a big opportunity to go with godash.
        
       | pcr910303 wrote:
       | (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
        
         | mpfundstein wrote:
         | 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 :-)
        
           | jerf wrote:
           | (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.
        
             | mpfundstein wrote:
             | 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
        
               | jerf wrote:
               | "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.
        
         | whateveracct wrote:
         | 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.
        
       | thatjamesdude wrote:
       | 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)
        
       | rad_gruchalski wrote:
       | 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.
        
       | domano wrote:
       | 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.
        
         | platinumrad wrote:
         | Variadic functions aren't even necessary for functional
         | programming in the first place.
        
         | jerf wrote:
         | Go does have variadic functions, but what are you counting as
         | "currying support"?
        
           | domano wrote:
           | Well, since functions are first class citizens nothing stops
           | me from turning a (a,b,c) into a (a)(b)(c)
        
             | jerf wrote:
             | 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: ht
             | tps://hackage.haskell.org/package/base-4.15.0.0/docs/Prelu.
             | ..
        
               | domano wrote:
               | Thanks, looking at it this way makes more sense!
        
       | svnpenn wrote:
       | 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...
        
         | aliyfarah wrote:
         | 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.
        
           | ngc248 wrote:
           | i think what he meant was not leetcode the service, but
           | leetcode as in elite code
        
         | ebingdom wrote:
         | 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.
        
           | mappu wrote:
           | All true, but the dichotomy here isn't between functional and
           | imperative - it's between imperative, and a mix of both.
        
           | ramenmeal wrote:
           | In this article, which code sample would you say is more
           | readable? The imperative style or the functional style?
        
             | viraptor wrote:
             | 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)
        
             | ebingdom wrote:
             | 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.
        
           | cle wrote:
           | 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.
        
             | ebingdom wrote:
             | 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.
        
             | nexuist wrote:
             | 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?
        
               | cultofmetatron wrote:
               | 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.
        
             | kaba0 wrote:
             | 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.
        
               | cle wrote:
               | I'm not saying to never use abstractions. There's a
               | balance.
        
           | amw-zero wrote:
           | > 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.
        
             | ebingdom wrote:
             | > > 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.
        
       | cle wrote:
       | 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.
        
         | eru wrote:
         | > [...] 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..
        
         | haolez wrote:
         | 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?
        
           | Zababa wrote:
           | > 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.
        
           | munificent wrote:
           | _> 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?
        
         | klodolph wrote:
         | 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.
        
           | duped wrote:
           | The vast majority of the usage of generics in Rust are as
           | replacements for inheritance or dynamic dispatch. It's not
           | very different structurally.
        
           | cle wrote:
           | Agree, this is my hope. Seeing the same thing play out in
           | other language ecosystems though, I'm just not very
           | optimistic about it.
        
           | masklinn wrote:
           | > 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>.
        
             | klodolph wrote:
             | 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.
        
               | masklinn wrote:
               | > 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.
        
         | judofyr wrote:
         | 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").
        
           | cle wrote:
           | 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.
        
             | [deleted]
        
             | moldavi wrote:
             | 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?
        
               | pharmakom wrote:
               | Except you still have to write out the typed
               | implementation - even if it is small. In Java this is not
               | necessary.
        
               | cratermoon wrote:
               | 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).
        
               | masklinn wrote:
               | > In a weird way, with this technique, did Go have
               | "manual generics" all along?
               | 
               | In the exact same way every langage without generics had
               | generics all along: not in any meaningful way.
               | 
               | You could also use codegen hook to instantiate your
               | pseudo-generics after all: https://www.reddit.com/r/rust/
               | comments/5penft/comment/dcsgk7...
        
             | nicoburns wrote:
             | > 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.
        
               | [deleted]
        
               | cle wrote:
               | 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^(ip)" is to "1".
        
               | nicoburns wrote:
               | If you're writing generic code using interface{} and then
               | wrapping that in concrete wrappers then you're actually
               | doing both!
        
               | cle wrote:
               | 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.)
               | 
               | 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.
        
               | [deleted]
        
             | hajile wrote:
             | That's just going back to dynamic typing.
        
               | cle wrote:
               | 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.
        
         | jshen wrote:
         | 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.
        
           | gher-shyu3i wrote:
           | > 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
        
             | [deleted]
        
           | kaba0 wrote:
           | 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.
        
           | edem wrote:
           | It is no surprise that Clojure was the easiest to read. It is
           | composed of S expressions and some syntactic sugar.
        
         | sudhirj wrote:
         | 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.
        
         | whateveracct wrote:
         | > 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.
        
           | cle wrote:
           | 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.
        
           | jackcviers3 wrote:
           | 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?
        
       ___________________________________________________________________
       (page generated 2021-05-27 23:03 UTC)