[HN Gopher] Go proposal: new package to provide generic slice fu...
       ___________________________________________________________________
        
       Go proposal: new package to provide generic slice functions
        
       Author : todsacerdoti
       Score  : 201 points
       Date   : 2021-05-05 11:23 UTC (11 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | specialist wrote:
       | Language enhancement proposals should also specify impact and
       | expected benefit.
       | 
       | How much are slices used in other languages? A little? A lot?
       | 
       | We now have a zillion open source projects. Someone could semgrep
       | that mountain, do some analytics. Figure out if adding slices, or
       | whatever, is worth the bother.
        
       | the-smug-one wrote:
       | >The higher-order function style also tends to be much slower,
       | 
       | This shouldn't be the case. A HoF-call should be optimized into a
       | JMP or be inlined directly. The issue of performance is more
       | likely to come from non-mutating functions operating on
       | datastructures which had mutation in mind.
        
         | creata wrote:
         | Here's my understanding: to get higher-order function calls to
         | compile to fast code, you _need_ some kind of compile-time code
         | expansion: templates in C++, specialization in Haskell,
         | monomorphization in Rust, etc. But if you make it _too_ easy to
         | have code expand into multiple instances, then you lose the
         | fast compile times that Go 's famous for, and that's a hard
         | balance to strike.
        
           | the-smug-one wrote:
           | Given a call map(a, f) and a loop for _,e := range a { f(e);
           | } both ought to have the same code size. Yes, map(a, f) will
           | need to be inlined, but the for-loop is already inlined.
        
             | creata wrote:
             | You're right. To be clearer, the emphasis was on making it
             | _too easy_ to specialize: people don 't just stop at maps
             | and filters, they specialize _everything_ , and that can
             | have a significant impact on compilation times. For
             | example, in Rust, people write stuff like [0] for the
             | tiniest bit of syntax sugar, and they often don't bother
             | extract out the non-generic part.
             | 
             | [0]: https://news.ycombinator.com/item?id=24088407
        
               | ben0x539 wrote:
               | The current idiomatic go style is to write your own for-
               | loops for everything. Isn't that just manual
               | specialization executed by the programmer rather than the
               | compiler?
        
       | [deleted]
        
         | solosoyokaze wrote:
         | A language that is functional from the ground up can be very
         | beautiful, concise and a pleasure to program in. I agree with
         | you though, that bolting functional programming onto Go will
         | make it worse. Especially with the millions of lines of legacy
         | Go code out there, it would essentially become an entirely
         | different language.
        
       | bfrog wrote:
       | I found it funny the first comment seemed to deride the use of
       | function pointers, compare, map, and filter as too different than
       | the for loop centric Go. Even C provides sort with a function
       | pointer for compare...
        
         | asdfasgasdgasdg wrote:
         | I've seen this type of argumentation around go and it boggles
         | my mind every time. Saying "we are not sure if we'll need a
         | version of this function that accepts an override to the type's
         | default comparison" only makes sense if you've never used a
         | programming language other than go. Or the function sort.Slice
         | for that matter.
        
           | nitrix wrote:
           | It's always the same thing.
           | 
           | People find a language/library too complicated, so they go on
           | their own to write a simpler version, only to find out that
           | they missed very important cases that prevents it from having
           | the same qualities.
           | 
           | Grudgingly, they then gradually re-introduce what they
           | initially strongly opposed against, but now they also have to
           | work around their current design and bogus abstractions that
           | wont let that happen easily.
           | 
           | That's going to create a lot of cruft, edge cases and API
           | complications, to the point where a bystander might decide
           | it's too complicated and, they too, go on their own write a
           | simpler version.
           | 
           | Go hasn't learned a single thing.
        
             | bollo wrote:
             | Times and paradigms change. We need to thank these people
             | or we would still be using Java EE
        
               | mohanmcgeek wrote:
               | More importantly the hardware changes and the patterns
               | designed to plug platform shortcomings would have to go
               | away.
        
               | pjmlp wrote:
               | I rather use JEE, now JakartaEE, as Go as yet to offer
               | anything that has parity with it.
        
             | choeger wrote:
             | But at least it keeps grad students busy in the programming
             | language departments ;). Imagine that all we had was
             | Haskell and maybe a strict variant of it. These guys would
             | quickly run out of topics that could be explained in less
             | then 5min to a casual developer.
        
               | jolux wrote:
               | I'm not denying that there are a lot of difficult
               | concepts and million dollar words in advanced functional
               | programming but I will say that I personally took years
               | to understand what was going on with OOP when I was
               | learning programming. Functions made sense, classes and
               | objects and inheritance and subtype polymorphism did not.
        
               | bfrog wrote:
               | Pure functions are incredibly simple thing to understand,
               | much like an algebraic equation you provide inputs and
               | get an output.
               | 
               | There's a lot to love and learn from functional
               | programming.
        
               | [deleted]
        
             | pjmlp wrote:
             | Two anecdotes why I hate the way they went with
             | //go:generate as workaround.
             | 
             | Borland first attempt to C++ "generics" was the initial
             | release of Borland International Data Structures 1.0 (BIDS)
             | around 1990, where they used the preprocessor to generate
             | multiple copies, something like                   #define
             | LIST_T int         #define LIST_TYPE MyIntList
             | #include <bids/list.h>
             | 
             | Rice and repeat for all required types, when BIDS 2.0 came
             | out this was already replaced by experimental templates
             | support.
             | 
             | Around 2005 it was common to use Eclipse EMF framework,
             | alongside plugins to generate Java code (<= 1.4) that would
             | create type safe subclasses from collections with Object.
             | 
             | So learning is a hard process, then again this was their
             | point of view when C was created,
             | 
             | > Although we entertained occasional thoughts about
             | implementing one of the major languages of the time like
             | Fortran, PL/I, or Algol 68, such a project seemed
             | hopelessly large for our resources: much simpler and
             | smaller tools were called for. All these languages
             | influenced our work, but it was more fun to do things on
             | our own.
        
             | LanceH wrote:
             | > Go hasn't learned a single thing.
             | 
             | How do you explain the success then?
             | 
             | Go hasn't tried to be all things to everyone. There are
             | lots of things out there that need simple solutions and Go
             | provides for that with and nice middle ground of dev and
             | computer performance.
             | 
             | If I want to read from one computer, write to another
             | computer, concurrently, with low footprint, and speed, it's
             | a great tool.
             | 
             | If Go has learned nothing, are Go programmers just picking
             | something more difficult that performs worse? I've found it
             | has dislodged services that were previously written in
             | Node, Ruby, or a whole J2EE stack. Sure there are people
             | out there trying to make it all things to everyone and it
             | fails in some of those things, but it does great at others.
        
               | kubb wrote:
               | > How do you explain the success then?
               | 
               | A megacorp funding 100 engineers to work on the compiler,
               | libraries and tooling for 10 years
        
               | int_19h wrote:
               | I suspect that much of Go's popularity stems from one
               | thing that's completely unrelated to its language design
               | woes: its ability to generate standalone binaries with
               | minimal dependencies (and specifically none on Linux,
               | which is prevalent for container use).
               | 
               | Go itself is not particularly simple, actually. It's
               | _simplistic_ , meaning that you have to jump through more
               | hoops to do common things. For a concise example,
               | consider adding an item to a slice in Go vs adding an
               | item to a collection in pretty much any other modern
               | language.
        
         | int_19h wrote:
         | The specific argument made there is even stranger. It's
         | essentially saying that if HOFs are adopted, they will
         | inevitably replace for-loops for common cases - and _that_ is a
         | bad thing! In other words, it 's trying to block a feature that
         | it specifically acknowledges would be highly popular in the
         | name of simplicity and purity.
        
         | frou_dh wrote:
         | > Even C provides sort with a function pointer for compare...
         | 
         | That's been in non-generic Go for a while (passing a closure
         | for sorting): https://golang.org/pkg/sort/#Slice
        
       | p5v wrote:
       | Great to hear! This only encourages me to continue writing my
       | book on generic programming with Go
       | (https://gumroad.com/l/generic-golang)
        
       | qsort wrote:
       | I agree with the goal of the package, but I feel it doesn't do a
       | good enough job of picking a lane between the imperative and
       | functional approach.
       | 
       | If you're going functional, a list is both a functor and a monad,
       | so you definitely need at least map and flatMap for the
       | abstraction to make sense.
       | 
       | On the other hand there are a lot of "in place", "insert", etc,
       | which encourage working with mutation.
       | 
       | You probably need a bit of both, but I'd like a more top-down
       | design better.
        
         | parhamn wrote:
         | > The higher-order function style also tends to be much slower,
         | so when writing a loop there's a decision burden every time
         | between saving a line or two and writing fast code. (TIMTOWTDI
         | is not really the Go ethos.) And programmers usually prefer
         | concision, so the net result is often slow, HOF-heavy code.
         | 
         | This is very real. We do it all the time in JavaScript:
         | 
         | Object.entries({...spreadMerge,
         | ...secondSpread}).map().filter().find(x)
         | 
         | Not once do I think about how many things are initialized,
         | iterated over, etc. Rarely would I do something like this in
         | Go.
         | 
         | Whether it matters is a good question, but the point is true:
         | language facilities make you care more or less about
         | performance depending on what it does.
        
           | jrimbault wrote:
           | Rarely does a week go by without me wishing (for a few
           | seconds) those js methods were all lazy.
        
           | jolux wrote:
           | This is a problem of optimization, not language design.
           | There's no inherent reason a chain of functions like that has
           | to be less performant than a for-loop. You need high-
           | performance iterators in the language, and a good compiler.
        
             | throwaway894345 wrote:
             | This seems like the "sufficiently smart compiler" argument.
             | A sufficiently complex compiler could certainly optimize
             | this away, but building and maintaining such a compiler
             | while also keeping compilation times low is an enormous
             | challenge. Rust does an impressive job here, but Rust was
             | built from the ground up to handle these problems and it
             | also trades compile times to achieve those optimizations.
        
               | pjmlp wrote:
               | Not really on this case, a common optimization pass on
               | functional programming languages since the 70's is to
               | flatten such kind of calls.
               | 
               | Rust long compile times are mainly caused by LLVM, and
               | the amount of IR that is given to it.
        
               | pornel wrote:
               | It's easy in functional languages where execution order
               | and side effects aren't an issue. In JS with prototype
               | monkey-patching you can't even know that the call to
               | `.map()`, or anything that it touches, won't suddenly
               | replace the implementation of the `.filter()` method that
               | follows it.
               | 
               | The large amount of IR given to LLVM is closely related
               | to the amount of abstractions that Rust uses. It's
               | especially relevant for iterators where a "simple" `for`
               | loop expands to `into_iter()`, `iter.next()`, `match`,
               | `drop`, and arithmetic expands to calls to overloadable
               | operator methods, all moves are `memcpy` to clean up, and
               | so on.
               | 
               | https://rust.godbolt.org/z/71aEnf9Ta
        
               | pjmlp wrote:
               | I remember the days when functional languages wasn't a
               | synonym for like Haskell does it.
               | 
               | Speaking of which just use OCaml bytecode backend or
               | Haskell interpreter to see how slow complex languages
               | are.
               | 
               | This is what is missing to Rust and cranelift might help
               | to deliver, alongside not having to keep compiling the
               | whole world from scratch.
        
               | 8fGTBjZxBcHq wrote:
               | I don't know about JS's particular challenges here and I
               | believe you but a lot of languages with a lot of other
               | features also do this optimization. It's not just the
               | hardcore functional ones though admittedly maybe mostly
               | in the ones most influenced by them.
        
               | bollu wrote:
               | Can you point me to a paper from the 70s that explains
               | how to flatten such calls? The best that I know is
               | "stream fusion, to completeness" which was published in
               | something like 2019!
        
               | pjmlp wrote:
               | You can get the book
               | 
               | "Lisp on Small Pieces" from 1994
               | 
               | https://www.amazon.com/exec/obidos/ASIN/0521562473/acmorg
               | -20
               | 
               | It is not 70's, but 26 years should be enough to get hold
               | of such knowledge.
               | 
               | Or "Modern Compiler Implementation" from 1998, a little
               | more young.
               | 
               | https://www.cs.princeton.edu/~appel/modern/
               | 
               | Or rather "The Implementation of Functional Programming
               | Languages", from 1986, now getting closer to 70's.
               | 
               | https://www.microsoft.com/en-us/research/publication/the-
               | imp...
               | 
               | I can still have a look into my SIGPLAN paper collection
               | to dust off some ML papers.
        
               | [deleted]
        
               | Rusky wrote:
               | This is one of the idioms that leads to large amounts of
               | IR!
               | 
               | Avoiding that means doing the flattening earlier, in a
               | higher level IR.
        
             | Cthulhu_ wrote:
             | One of Go's objectives is to have a simple and fast
             | compiler; emphasizing simple and readable code will ensure
             | that. It's one reason why type arguments were put off for
             | as long as they have been.
        
               | dmitriid wrote:
               | `.map().filter().find(x)` is significantly more simple
               | and readable than for-looping for every small thing in
               | Go.
        
               | twobitshifter wrote:
               | In the case we'd need a little context of what the map,
               | filter, and find operations are, but if map is multiple
               | by 2 and filter is less than 200 you can just replace the
               | filter and map with a single if in the for loop that
               | performs the multiplications and check and return
               | immediately when you find X. That's pretty obvious,
               | always takes only one run, doesn't reallocate, and
               | doesn't add concepts, which is of course adding
               | complexity.
        
               | stouset wrote:
               | Yes and we could replace all of that with an
               | implementation in assembly which both compiles _and_ runs
               | faster.
               | 
               | But we don't do that because for the entire history of
               | computing, we've come to realize that abstractions enable
               | us to reason about programs at a higher level without
               | having to be bogged down in irrelevant details and that
               | getting those details right _once_ lets us avoid the
               | inevitable bugs that occur while reimplementing them
               | thousands of times.
               | 
               | By absolutely every objective measure we know in software
               | engineering                  let triples = ints.map(|x| x
               | * 3)
               | 
               | is strictly superior to                   var triples =
               | []int         for value := range ints {
               | triples = append(triples, value * 8)         }
               | 
               | The fact that this even has to be argued is just bonkers
               | to me.
               | 
               | The second one is trivial to introduce bugs into, reading
               | it requires mentally filtering out 90% of the code as
               | irrelevant minutiae, and it's even worse from a
               | performance perspective unless you remember each and
               | every time to allocate a result array of the appropriate
               | length to avoid reallocation. None of the details of
               | allocating a result array or iterating over
               | indices/values are important from the purpose of
               | expressing the problem to be solved, and yet every time
               | someone has to read this solution those things are of
               | greater visibility than the actual business logic.
               | 
               | Worse, repetitive and unnecessary boilerplate like this
               | hides bugs. Did you catch the fact that I'm multiplying
               | by the wrong value in the golang version? Did you catch
               | that I'm accidentally tripling the _indices_ and not the
               | values themselves? If you did, would you at least
               | acknowledge that the first bug is much easier to identify
               | in the first example than the second, and that the second
               | bug is _strictly impossible_?
               | 
               | As an added bonus, the first one (in Rust at least) is
               | automatically vectorized using SIMD for optimal
               | performance. This happens even as you chain additional
               | operations to arbitrary complexity.
               | 
               | If you still somehow think the second version is
               | "better", then whatever your argument is can trivially be
               | repurposed to say this version is better still:
               | int* triples = malloc(sizeof(int) * array_len);
               | for (int i = 0; i < array_len; i++) {
               | triples[i] = array[i] * 3;         }
               | 
               | If those details of creating a result array and
               | explicitly enumerating are important, why isn't
               | maintaining your own counter and directly indexing? At
               | least the C version somewhat encourages you to allocate a
               | result array of the correct size.
        
               | throwaway894345 wrote:
               | I disagree. Fewer characters != simpler or even more
               | readable. Map/filter/etc are certainly more clever, and I
               | _enjoy_ writing clever code, but I reserve it for my
               | personal projects bprecisely because it isn't so clear.
               | Note that Python has these first class functions as well
               | as various kinds of comprehensions and still it's frowned
               | upon to use anything more complex than the simplest
               | comprehensions.
        
               | creata wrote:
               | Alright, if it's not too much trouble, can you show me
               | what the equivalent of the following code would be in Go?
               | I think this is extremely clear as-is, but I'd love to
               | see how you can apparently make it even clearer by
               | avoiding iterator combinators.
               | 
               | https://play.rust-
               | lang.org/?version=nightly&mode=release&edi...
        
               | throwaway894345 wrote:
               | Personally I think this is clearer, but it's quite
               | contrived either way:
               | https://play.golang.org/p/FpUWwm5yZ8n
        
               | creata wrote:
               | Nice! Out of curiosity, if the iterators _weren 't_ of
               | the same type (e.g. one was an iterator over the values
               | of a hashmap and the other was over the items in a list)
               | what would you have used instead of                   for
               | _, m := range maps { ... }?
        
               | throwaway894345 wrote:
               | Just as with Rust, you'd have to convert both maps to the
               | same type before you could iterate them over together (or
               | chain them, in Rust).
        
               | coder543 wrote:
               | If you could provide the hypothetical Rust code you're
               | referring to, that would probably make your question more
               | clear here. I know both Rust and Go rather well, and I'm
               | confused about which direction you're wanting to take the
               | code.
        
               | joelfolksy wrote:
               | This argument doesn't really have any content, because
               | people who advocate for programming with higher order
               | functions would _not_ agree that such code is more
               | clever; in fact, they would probably argue the opposite.
               | What 's the point of debating if you assume the people on
               | the other side already agree with you?
        
               | jolux wrote:
               | Map and filter are extremely simple, far simpler than a
               | for-loop. They can only do two things: transform items in
               | a collection, and remove items from a collection. After
               | mapping you know for sure that a collection will still
               | have the same number of elements. After filtering you
               | know that all the elements remaining still have the same
               | type. For-loops can do anything. It's a question of what
               | you're most familiar with and used to.
        
               | throwaway894345 wrote:
               | I'm not comparing for loops _in general_ with map or
               | filter, but rather the specific for loop boilerplate to
               | implement map and filter. Moreover, the complexity of map
               | /filter isn't internal, but rather decomposing or
               | breaking down one's problem into map/filter/etc
               | expressions. Often for loops are clearer, especially
               | since you can frequently collapse a map and filter into a
               | single for loop without thinking in map/filter/etc
               | primitives. This is especially true when you need to
               | abort the chain early (e.g., due to errors)--Rust's
               | FromIterator provides a clever solution to this problem,
               | but it's much harder to wrap one's head around than a
               | simple for loop with an early return branch.
        
               | jolux wrote:
               | I find that decomposing iteration into map and filter
               | expressions typically clarifies my understanding of the
               | problem and makes the resulting code more general too,
               | and I just disagree that for-loops are clearer. Combining
               | a map and filter into one loop body tends to create
               | specific, imperative code that doesn't need to be
               | specific or imperative. For one, loops require mutation
               | to have the same effect as filter and map, and as soon as
               | you're maintaining state, things get confusing. My usage
               | of higher order functions instead of for-loops is an
               | expression of my belief that programmers should always
               | use the least powerful construct they can to solve a
               | problem.
               | 
               | In general though I think the functions used in map and
               | filter should be total. Most languages are not expressive
               | enough to handle errors fluidly in long chains like that,
               | you can do it with monads but apparently everyone hates
               | those except me.
        
               | throwaway894345 wrote:
               | > I find that decomposing iteration into map and filter
               | expressions typically clarifies my understanding of the
               | problem and makes the resulting code more general too,
               | and I just disagree that for-loops are clearer.
               | 
               | Agree to disagree I guess.
               | 
               | > Combining a map and filter into one loop body tends to
               | create specific, imperative code that doesn't need to be
               | specific or imperative. For one, loops require mutation
               | to have the same effect as filter and map, and as soon as
               | you're maintaining state, things get confusing.
               | 
               | I agree that immutable, declarative code is easier to
               | reason about in general than mutable, imperative code,
               | but it's not a binary proposition. I posit the cost of
               | managing the tiny bit of very-local mutable state for a
               | for loop is a lower cost than trying to model a problem
               | in terms of map and filter. While I can appreciate the
               | elegance in "immutable always", I think it's too
               | puritanical for real world software development,
               | especially in these cases which are simply expressed as
               | for loops.
               | 
               | Note that languages like Python have strong support for
               | immutable expressions _and_ `for` loops, and it seems
               | very common (even idiomatic) to use imperative `for`
               | loops for anything more complicated than the simplest
               | list comprehensions.
               | 
               | > In general though I think the functions used in map and
               | filter should be total. Most languages are not expressive
               | enough to handle errors fluidly in long chains like that,
               | you can do it with monads but apparently everyone hates
               | those except me.
               | 
               | I think it's just that monads tend to allow for very
               | abstract code at the expense of understandability, and
               | the abstraction that monads facilitate is often well in
               | excess of what a given problem requires. If you're
               | writing code to maximize abstraction (which is a lot of
               | fun), monads (and map/filter/etc) are great, but if
               | you're trying to work with a team to build software to
               | solve real problems, you probably care a lot more about
               | readability than gratuitous abstraction and the monad
               | (/map/filter/reduce/etc) juice frequently isn't worth the
               | squeeze.
        
               | jolux wrote:
               | Python's FP capabilities are severely constrained by not
               | supporting multi-line lambdas or composition operators
               | (pipe, sequence, etc). It is not a good language for
               | functional programming at all. The community norms
               | explicitly push you away from using FP in it because of
               | how limited it is. We use it for wrangling large data
               | sets and tend to hate every minute of it.
               | 
               | My workplace uses Elixir exclusively in production for
               | our backend services, so we're all functional programmers
               | and we do not find that map, filter, and reduce make
               | things harder to read, quite the opposite. Breaking up
               | loops does not introduce cognitive overhead, it makes
               | every step in the chain more explicit. You can call this
               | personal preference but there's nothing concrete in your
               | belief that for-loops are more readable or easier for
               | teams of programmers to maintain, many people disagree.
        
               | throwaway894345 wrote:
               | > Python's FP capabilities are severely constrained by
               | not supporting multi-line lambdas or composition
               | operators (pipe, sequence, etc)
               | 
               | Hardly. You can absolutely have _multi-line_ lambdas,
               | just not _multi-expression_ lambdas (the real limitation
               | is that there are no try /except expressions so certain
               | fallible manipulations aren't so easily expressed).
               | Similarly, Python lets you overload operators so you
               | could always override `|` or whatever. Of course,
               | operators are just syntax sugar for function calls so
               | this strikes me as a cop-out. In either case, neither of
               | these excuses are pertinent to the community's rejection
               | of complex comprehensions or higher order functions.
               | 
               | > My workplace uses Elixir exclusively in production for
               | our backend services, so we're all functional programmers
               | and we do not find that map, filter, and reduce make
               | things harder to read, quite the opposite. Breaking up
               | loops does not introduce cognitive overhead, it makes
               | every step in the chain more explicit. You can call this
               | personal preference but there's nothing concrete in your
               | belief that for-loops are more readable or easier for
               | teams of programmers to maintain, many people disagree.
               | 
               | You could argue that any two models which evaluate to the
               | same thing are fundamentally equally easy to understand.
               | You could argue that monads are just as easy to
               | understand as a sequence of statements in an imperative
               | language, but experience suggests that people have a
               | harder time understanding the more abstract concept
               | rather than the more concrete concept. This shows up all
               | over--Newtonian physics are more easily understood than
               | the more abstract quantum physics and in mathematics
               | "more advanced" math tends to mean roughly "more
               | abstract".
        
               | iudqnolq wrote:
               | I generally agree with you, but it's nice that in Rust
               | you can use a for loop if it makes more sense. I mostly
               | end up doing this when I want to reduce to multiple
               | collections (put type a in here, type b in there) or call
               | functions that return different types of errors.
               | 
               | The other time it's very useful is when translating
               | algorithms specified in an imperative form. I translated
               | a bunch of algorithms specified as for loops to elixir,
               | and it was annoying to have an extra source of complexity
               | when comparing the implementations for correctness.
        
               | arethuza wrote:
               | I really like the method style of LINQ in C# - which I
               | would agree are much clearer than the equivalent explicit
               | loops.
        
               | patates wrote:
               | I really like chaining array methods but you should also
               | never forget it's incredibly easy to introduce crazy
               | computational complexity with them.
        
               | jolux wrote:
               | Not if you have solid lazy iterator support in the
               | language. If they're strict and every link in the chain
               | is adding O(n) to the complexity then yes, this is a
               | problem.
        
               | coder543 wrote:
               | Rust has lazy iterators, so a chain of iterator steps
               | like this has the same complexity as a single for loop
               | over the iterator. There's no reason Go _couldn 't_ do
               | that same thing.
        
               | stouset wrote:
               | In fact, Rust's version compiles down to pretty much the
               | optimal looping form. It will even vectorize your
               | iterations using SIMD.
        
               | throwaway894345 wrote:
               | Rust is _seriously cool_ in this regard, but Go aspires
               | to be different, namely more C-like and less C++-like.
               | This means a dumber, faster compiler that is easier to
               | intuit about, for better or worse. Rust is _such an
               | amazing piece of engineering_ (moreso an amazing process
               | and culture for producing an amazing language), but I
               | think Go's approach is right for a whole lot of software
               | --lots of things don't need to be insanely performant
               | (lots of things are chugging along in _Python_ , which
               | runs 2-3 orders of magnitude _slower_ than Go) but we do
               | need to be able to iterate on them quickly in a
               | development context involving dozens or hundreds of other
               | people.
        
               | kubb wrote:
               | This is the decade-long argument of "let's sacrifice all
               | our programmers to the devil so that we don't have work
               | as hard on the compiler". I don't buy it, and it has
               | never been demonstrated that the cumbersome style of
               | writing go programs is helping compilation times, yet
               | people keep saying it as if it's a fact.
        
               | jolux wrote:
               | The cumbersome style seems to have become a strong part
               | of the culture, almost a masochism about the limited
               | features of the language. I find this ironic because I
               | believe Go was intended to be highly pragmatic originally
               | but it seems to have evolved an almost Scheme-like
               | dogmatism about language complexity.
        
               | Serow225 wrote:
               | Thank you for naming this. I've always struggled with how
               | to convey this to people.
        
               | skywhopper wrote:
               | Do you have specific examples you're thinking of? Calling
               | it masochism is weirdly insulting. Adding complexity is a
               | one-way street. Languages live for decades. There's no
               | reason to rush adding features in a half-baked way, and
               | the tradeoffs in terms of compiler complexity are very
               | real.
        
               | stouset wrote:
               | Virtually every large go program I've read has
               | asymptotically reached 50%+ of its code being
               | if res, err := fn(...); err != nil {
               | fmt.Errorf("Look ma, I'm a human exception handler: ",
               | err)         }
               | 
               | This is held up as a positive thing, because handling
               | errors is explicit and good. Somehow, the Rust equivalent
               | of `?` is implicit and bad, despite desugaring to almost
               | literally the exact same thing and being actually
               | impossible to forget or overlook due to the return type
               | of the function enforcing a Result.
               | 
               | In the same breath, devs will assert that the language is
               | "low boilerplate" (despite 50%+ LOC being just one type
               | of boilerplate) because Error Handling is Important.
               | Which seemingly ignores that the important part of error
               | handling is _actually ensuring that errors are handled_
               | and not the mechanistic act of self-flagellation by
               | wrapping every line of code in nearly-identical error
               | handling ceremony. Which is unfortunate because golang
               | doesn 't do so great a job with the first part. It's
               | surprisingly easy to actually forget to do something with
               | the error while carrying on with a return value whose
               | semantics are undefined (unlike Rust's Result, where you
               | can have an error _or_ a value and not both).
        
               | throwaway894345 wrote:
               | I use Go a lot and I really like Rust, so maybe I can add
               | some context. Error handling boilerplate isn't a big
               | deal. Lines of code aren't a big deal. That's not where
               | your bugs are coming from and you aren't bottlenecked on
               | your typing speed. It's not a real problem, but rather a
               | dogmatic pursuit of terseness for its own sake. Rust is
               | an expression-oriented language--it needs something like
               | ? because without it we would have gratuitous nested
               | braces marching ever-rightward and that actually is
               | laborious to keep track of (deeply nested match
               | expressions). Go could drop some error handling
               | boilerplate with an ? operator, but it wouldn't benefit
               | as much from it as Rust did.
        
               | eweise wrote:
               | I used to hardly think about exception handling on the
               | jvm, mostly let them bubble up and be centrally handled
               | except in special cases. Now I'm always worried that I'll
               | forgot to manually handle the error in Go. Can't tell you
               | how many times I've gotten panics as well, from nil
               | objects.
        
               | skywhopper wrote:
               | The Go compiler is much much faster than C++ and Rust. I
               | don't think that's seriously disputed by anyone, is it?
               | 
               | There's no universally correct answer to the right
               | balance of "smart compiler" vs "simple language". Having
               | worked with Go on a daily basis for five years now, I
               | really like the simplicity of the language. Compared to
               | the cognitive load imposed by the language when using C++
               | and Rust, I much prefer the naive straightforwardness of
               | Go. There's no devilry about it.
        
               | kubb wrote:
               | Go vs C++ is not a fair comparison, because C++ is a
               | badly designed language from the 1980s. It also serves a
               | different domain. Certain applications of C++ are not
               | serviceable by Go.
               | 
               | Go vs Rust is not a fair comparison, because Rust's type
               | system is much more powerful that Go's and enables
               | applications that Go's doesn't (like embedded systems
               | programming). Also some of the things I said about C++
               | apply here.
               | 
               | There are also features of Go absent from C++ and Rust
               | (reflection, garbage collection). So it's really puzzling
               | that you'd pick those two.
               | 
               | To have a fair comparison, you'd need to look at
               | something like Kotlin sans the class system. There Kotlin
               | is a clear winner, and with the same engineering
               | resources put behind it, a cut-down version of Kotlin
               | would easily be able to achieve build times in the
               | ballpark of generic-enabled Go.
               | 
               | Despite its claimed simplicity, Go has inconsistent
               | design and bug-prone unwieldy features (try to write code
               | that merges streams of data from two channels without
               | blocking unnecessarily). It also has unnecessary
               | constructs that contradict its claim to include only
               | what's necessary (interfaces are just structs of function
               | pointers, type methods are just functions with a
               | glorified first argument).
               | 
               | It may be enough to write one-off command line
               | applications, but it doesn't give developers the tools to
               | create complex distributed systems in a systematic, clean
               | way, which was supposed to be its main area of
               | application.
        
               | geodel wrote:
               | > and with the same engineering resources put behind it,
               | a cut-down version of Kotlin would easily be able to
               | achieve build times in the ballpark of generic-enabled
               | Go.
               | 
               | So basically no real example. It is only what would be
               | possible if things were to happen certain way.
               | 
               | > It may be enough to write one-off command line
               | applications, but it doesn't give developers the tools to
               | create complex distributed systems in a systematic, clean
               | way
               | 
               | This is just arrogant opinion. Distributed tools and
               | applications like NATS, Kubernetes, Docker, CockroachDB
               | and so many other exist and written in Go. Developers who
               | can't write distributed systems themselves can ofcourse
               | buy or license tools from vendors. And at that point,
               | honestly, it could have been written in any language.
        
               | erik_seaberg wrote:
               | If your team has to write and review boilerplate by hand,
               | that's millions of times slower than a computer
               | generating it. The compiler only looks faster when we
               | exclude all that wasted effort.
        
             | masklinn wrote:
             | > This is a problem of optimization, not language design.
             | 
             | It is also a problem of langage design though: JS's HoFs
             | are spec-defined as returning Arrays, which makes fusion
             | way more complicated.
             | 
             | Internal / external iteration also have different
             | optimisation profiles e.g. Rust initially used internal
             | iteration, switched to external because that was much more
             | conducive to optimisations, but re-introduced an internal
             | supplement because some combinations / patterns optimise
             | much better through internal iteration.
        
               | jolux wrote:
               | This is true, it does depend on how the implementation
               | works.
        
               | creata wrote:
               | > switched to external because that was much more
               | conducive to optimisations
               | 
               | Source? I thought conventional wisdom was that internal
               | iteration was much easier for compilers to digest.
        
               | masklinn wrote:
               | https://mail.mozilla.org/pipermail/rust-
               | dev/2013-June/004364... Was probably the main starting
               | point for that change. Though I misremembered
               | performances being the main gist of the switch,
               | ergonomics and flexibility were.
               | 
               | With https://www.reddit.com/r/rust/comments/5ez38g/rusts_
               | iterator... as the... not counterpoint but reintroduction
               | of internal iterators.
               | 
               | Linking to the Reddit thread rather that the article
               | directly because lots of comments from important rust
               | folks.
        
         | sabellito wrote:
         | I think it's to do with the community and what people who are
         | really into Go are interested in.
         | 
         | The first reply to the thread deems "map" and "filter"
         | borderline-useful, but "reduce" not worth it - that type of
         | misunderstading just shows that the author (and the 60+ people
         | who +1'd it) didn't spend enough time understading functional
         | constructs yet.
        
           | masklinn wrote:
           | Sounds about right. Python 3 demoted reduce from builtin to
           | stdlib because it saw very little use after the introduction
           | of the more common use cases as builtins.
        
           | foldr wrote:
           | Reduce is basically "loop over this list while maintaining
           | state". If you have loops and state built in, it doesn't buy
           | you a whole lot.
           | 
           | I don't think the aim of the package is to move Go towards a
           | more functional style. Rather, it's to provide a set of
           | utility functions that mesh with the style of existing Go
           | code. For example, it would be useful to have a utility to
           | 'pop' an element from a slice, even though that's not a
           | particularly functional idiom.
        
             | sabellito wrote:
             | I definitely wasn't arguing about what should or should not
             | be in the package. I just replied to the parent comment
             | with my idea as to why the discussion around this point is
             | happening in the proposal.
        
           | tsdlts wrote:
           | Or maybe they understand them perfectly and have come across
           | enough obtuse code using reduce to not want it as part of the
           | language.
        
             | sabellito wrote:
             | Everyone draws the "code that I like looking at"-line at
             | different places.
             | 
             | Despite our wishes around that, functional constructs do
             | have a hierarchy of abstraction vs power, and skipping a
             | layer is the type of design that backfires in the end, like
             | with Java doubling down on the collection APIs without
             | introducing monads explicitly for users.
        
               | creata wrote:
               | > Java doubling down on the collection APIs without
               | introducing monads explicitly for users.
               | 
               | I'm not necessarily disagreeing with you, but how has
               | that backfired for Java? It seems as popular as ever, and
               | I don't hear many people lamenting the lack of monads in
               | Java.
        
               | sabellito wrote:
               | Ah thanks for pointing that out as I didn't make myself
               | clear. I definitely didn't mean Java as a whole, as it is
               | indeed a tremedously successful platform and language.
               | 
               | I meant specifically the lack of flexibility in some
               | regards because of the missing of monads and friends.
               | 
               | For example, we have a few newer languages, TypeScript
               | and Kotlin come to mind, implementing the "null
               | operator":                  thing?.fieldA?.fieldB
               | 
               | While this is quite handy, and people unfamiliar with the
               | Option monad will think that this new syntax is really
               | cool, it poses two limitations: 1. It doesn't compose
               | with other abstractions 2. I can't use this for my own
               | abstractions
               | 
               | If the language designers had chosen to take one step
               | back and implemented it in a more generic way (in some
               | sense, in the same as my other comment in this thread
               | about implementing "map" without "reduce"), the feature
               | could've still been implemented in the language, but
               | without the limitations.
               | 
               | I'm not sure what you meant with this point:
               | 
               | > people lamenting the lack of monads in Java.
               | 
               | Do you mean that, because you don't see people lamenting,
               | it's not a useful feature?
        
               | creata wrote:
               | > It doesn't compose with other abstractions
               | 
               | But monads don't compose with each other, do they? I
               | thought you needed something more (monad transformers,
               | algebraic effects, etc.) to get things to compose. Or
               | maybe you meant something else by composition.
               | 
               | > I can't use this for my own abstractions
               | 
               | Yep. But Java doesn't even let you overload addition
               | afaik, so being able to overload ? is pretty much a pipe
               | dream.
               | 
               | > Do you mean that, because you don't see people
               | lamenting, it's not a useful feature?
               | 
               | No, it's just that if something "backfired" for Java, I'd
               | expect to hear people complaining about it.
        
           | majewsky wrote:
           | This is not about misunderstanding. The author argues that
           | heavy map/filter/reduce does not fit with the general style
           | of Go programs. As a heavy Go user, I absolutely agree.
           | 
           | There is nothing preventing people from writing their own
           | reduce() in their own utilities package, and I expect a
           | thriving ecosystem of "even-more-functional-constructs"
           | packages to spring up as soon as generics hit stable.
           | 
           | The discussion in this proposal is not about this. It's only
           | and entirely about what the stdlib should provide, and that
           | is much more a question of practical and idiomatic usage than
           | it is about one's understanding of a theoretical CS syllabus.
        
             | marcus_holmes wrote:
             | This is, in part, why I'm generally not in favour of
             | generics in Go.
             | 
             | Go has a common style across most code bases I've seen.
             | Partly that's because Go is opinionated about style, and
             | doesn't really support doing things any other way. I don't
             | see this as a bad thing, though that may well be because
             | Go's style happens to be similar to my own. I have friends
             | who learned to code on Ruby and JS and who don't like Go at
             | all, because it doesn't really support the way they like to
             | write code.
             | 
             | Adding the ability to write functional-ish code to Go is
             | going to fragment the style. There will be code bases
             | written in functional-ish Go that are difficult to
             | understand for people used to a more imperative style. And
             | vice versa (though I suspect imperative is easier to
             | understand). I don't see this as a good thing. It's kinda
             | "if you want to write functional-ish code, why not pick a
             | language that you can do that in, instead of demanding that
             | Go supports it?". But I realise this is probably a lost
             | cause, and we're going to see that wave of "even-more-
             | functional-constructs" become so common that we end up with
             | JS-like library churn.
        
               | qsort wrote:
               | I'm not really a Go expert, but I've dabbled a little bit
               | in it and I like it a lot. Why do you believe generics
               | specifically would cause fragmentation? Isn't it more of
               | a standard library issue?
               | 
               | Probably I'm looking at it the wrong way, but it looks
               | to me like it's a relatively harmless language extension
               | with the added bonus of correctly typing stuff normally
               | cast as interface{}.
        
               | throwaway894345 wrote:
               | Probably because generics allow for many different
               | programming paradigms, and different packages will use
               | different paradigms, requiring developers to understand
               | all of them and integrate them. In addition to varying
               | paradigms, packages will also vary in their degrees of
               | abstraction--generics permit very abstract code while
               | idiomatic Go code is not particularly abstract. Note that
               | I'm not arguing that these tradeoffs aren't worth the
               | while, just that they do in fact exist.
        
               | marcus_holmes wrote:
               | I've been writing Go for 8+ years now, I guess. In all
               | that time I've maybe hit 3 or 4 problems that generics
               | would have helped with. And there were always workarounds
               | (tending to be heavy on the boilerplate). This is fairly
               | common, from the other Gophers I've talked to.
               | 
               | The problem with generics is they're a hammer, and
               | everything is going to look like a nail. Instead of being
               | a rarity, and everyone basically writing idiomatic Go as
               | normal and only pulling out the generics when really
               | needed, this functional-ish style that relies on generics
               | (as in TFA) will become common.
               | 
               | Devs who are used to writing array.map code in JS will
               | start on Go and immediately find the slices package in
               | the standard lib, and create a generic for _every single
               | slice_ they use, because that 's what they're used to and
               | how they think it's meant to be used. For...range loops
               | will look as old-skool and primitive as for loops look in
               | modern JS (despite all their advantages).
               | 
               | We already have the "learning path" of newbie Gophers,
               | whose first question is always "which framework should I
               | use?", and it's an uphill battle getting them to accept
               | that they should just use the standard lib. Trying to get
               | them to accept that they basically never need generics is
               | going to be harder.
               | 
               | Old crusty Gophers like me, who are used to and like the
               | simplicity of idiomatic Go, will be left shouting into
               | the void. Again.
               | 
               | edit: typing stuff as interface{} is also one of those
               | things that you learn to get past. I'm working on a
               | ~80Kloc Go code base at the moment, and have not had to
               | resort to interface{} once yet. Interfaces are way more
               | powerful than they appear at first. I'm not saying
               | interface{} doesn't have its place, and it's used heavily
               | in the standard library, but it's not as common as you'd
               | think at first glance.
        
               | jasonwatkinspdx wrote:
               | The biggest problem is the opportunity cost, and you may
               | not be aware of just what you're giving up if you've not
               | done substantial work in other ecosystems over the years.
               | 
               | The two big places this shows up in golang, imo, is the
               | lack of high quality data structure libraries, and the
               | lack of widely used standard library for routine channel
               | management tasks.
               | 
               | The former is a huge opportunity cost. If I'm coding on
               | .net, jvm, c, or c++ I have a variety of state of the art
               | data structures available. Some of these structures are
               | extremely difficult to develop, so the "just write your
               | own boilerplate" approach is completely insufficient.
               | Point to code generation templates all you want, the
               | reality is that this problem is preventing golang from
               | improving much past using mutex protected hash maps and
               | google's interface{} typed btree. Meanwhile, if we look
               | at say .net, you have a very high quality lock free
               | concurrent hash table available that scales to massive
               | heap sizes. This is no niche thing: the bw-tree /
               | cosmosdb storage engine is entirely designed around the
               | capabilities of this lock free map. It's easily
               | responsible for 100's of millions of dollars of value to
               | software in that ecosystem now.
               | 
               | The lack of simple, clear, and useful channel lifetime
               | management functions is another rough edge that bites
               | many go programmers daily. The many abuses of Context
               | make this problem quite clear.
               | 
               | I understand the sentiment of not wanting golang to turn
               | into the Stephanov style C++ generics. But I think that's
               | an imaginary fear. The golang generics design is quite
               | different and far more "gopher" for lack of a better
               | term. The problems golang is facing due to poor type
               | flexibility in this area are not imaginary: they are
               | endemic.
        
               | marcus_holmes wrote:
               | I used to code in C# before Go. This isn't my first
               | rodeo. Or even my second.
               | 
               | And I get that Go lacks in some areas. That's fine. All
               | languages lack in some areas. Go wasn't designed to be
               | all things to all people. It's very good at writing
               | servers in. And there's a few projects written in Go that
               | have proven to be pretty popular and scaled well ;)
               | 
               | It's not really YAGNI, which Go has been accused of (a
               | lot), with some justification. It's more that simplicity
               | is valuable, but it has a cost. Keeping Go simple is
               | good, but the price of that simplicity is sometimes high.
               | As you say, there is an opportunity cost.
               | 
               | I still think that cost is worth paying, and I'm not
               | convinced (yet - I may be in time) that the reduction in
               | simplicity that comes with generics is a price worth
               | paying for the flexibility it brings.
        
             | sabellito wrote:
             | If you re-read the comment I was replying to I don't think
             | your comment as a reply to mine makes a lot of sense.
             | 
             | I'm not arguing what's good and bad, nor what should or
             | should not be in the Go language.
        
             | jerf wrote:
             | I need to update this for the latest generics syntax, but I
             | wrote this a while back: http://www.jerf.org/iri/post/2955
             | "Why Go Getting Generics Will Not Change Idiomatic Go"
             | 
             | I do not understand the people who think that functional-
             | esque programming is the only and best way to do things who
             | are apparently just sitting and waiting for Go to be able
             | to do it. Are you forced to use Go or something? This is an
             | honest question, btw, and I am legitimately interested in
             | answers. If the only reason or the biggest reason you're
             | not using Go is that you can't import map-centric
             | programming into it, why not use any of the many languages
             | that _support_ that rather than griping about a language
             | where it 's still ultimately going to be bodged on? Again,
             | honest question, because there's no shortage of such
             | languages.
             | 
             | My perception is that Go isn't _that_ popular that there 's
             | a lot of people forced to use it against their will because
             | it's just The Language.
             | 
             | Anyhow, even after generics drop, it still isn't going to
             | be any fun. I expect to use a map here or there often
             | enough that it'll be nice to have it in the standard
             | library but there's no realistic way that it's going to
             | become "the best way to program in Go". If you're waiting
             | to try it out after the generics drop, I would suggest
             | trying something else instead; Elixir, Nim, Crystal, Pony.
        
               | 1_person wrote:
               | Your statements in the blog about performance of that
               | pattern are not accurate to the best of my knowledge.
               | 
               | A program structured as a sequence of short, tight loops
               | over vectors/slices as described is more or less hitting
               | the performance sweet spot of modern microarchitectures.
               | 
               | The general form of a short loop over a vector plays to
               | the strengths of branch prediction and the instruction
               | cache while the temporal and spatial locality of the
               | memory accesses are conducive to caching and pipelining.
               | 
               | Moreover, there's usually no need to allocate or copy the
               | array in that sort of data flow. I mean, unless you're
               | chasing worst case performance to make a point or
               | something. Slice the buffers out of a pool and allow
               | ownership of the data to follow the execution context,
               | then you're free to modify it in place.
               | 
               | The above points cover 3-4 orders of magnitude real world
               | performance.
               | 
               | It is true that there are some issues with the optimizer
               | working in this pattern but I have only seen performance
               | severely degraded in this pattern by compile time type
               | ambiguity -- some but not all interface{} parameters,
               | anonymous functions / closures in scopes with dynamic
               | type. Interface types with a pointer receiver do not
               | typically encounter these specific issues, and I believe
               | the empty interface type has much better performance
               | since ~1.12 or so as well.
        
               | Serow225 wrote:
               | I dunno, for some domains it feels like 80% of the job
               | openings are using Go.
        
               | hughrr wrote:
               | Your blog post was a great read. Completely agree. I've
               | seen so much stuff turn to excrement the moment FP is
               | applied.
               | 
               | I'm writing a complex aggregator in go and the
               | abstraction is buffered channels. There is no map or
               | filter in sight. It is not needed.
        
               | morelisp wrote:
               | > Are you forced to use Go or something?
               | 
               | I mean, yes, most companies have a relatively small list
               | of supported languages, whether explicitly or
               | practically. If someone at our company wants to write a
               | program that does some binary data munging that's even
               | more frustrating in Java, and needs to do it faster than
               | Python allows, they have to use Go. We will not let them
               | use C or C++, because most of the team will not
               | understand it and it won't be able to integrate with any
               | of our standardized libraries or CI infrastructure.
        
               | mohanmcgeek wrote:
               | Many problems for which you might end up using something
               | like Kafka streams just lend themselves nicely into the
               | streams/collections -like style
               | 
               | I am happy using Go right now when they don't have map..
               | but including map and not including reduce/flatten is
               | just odd
        
               | jerf wrote:
               | "Many problems for which you might end up using something
               | like Kafka streams just lend themselves nicely into the
               | streams/collections -like style"
               | 
               | Have you actually taken the time to write out what it
               | looks like, as I have in that blog post above, though?
               | It's not pretty. Any sane Go programmer is not going to
               | want to use this for very much. (I write that carefully.
               | I anticipate using it a non-zero amount myself. But it's
               | not going to be a _lot_.)
               | 
               | I understand liking map/filter/reduce, even if it is the
               | beginning of functional programming rather than the end.
               | I don't understand absolutely demanding it in languages
               | where it doesn't fit well and insisting on using it _no
               | matter the cost_.
        
               | MAGZine wrote:
               | it's pretty clear to me. people coming from other
               | languages are used to typing less characters and
               | expressing their ideas like that, and they want to type
               | less characters in go.
               | 
               | for go, a language that idiomizes variable names like
               | `f`, you think that people would be more on board with
               | fewer characters. There are many other areas where go has
               | chosen to avoid tidying up the syntax/making lives easier
               | in the name of simplicity, though I digress.
               | 
               | As for your earlier question of "why don't you pick
               | another language," I think this is a silly question to
               | ask. The vast majority of people working for hire are not
               | picking their language. If you have a few hundred kloc
               | written in go, I doubt very much people are in a position
               | to casually introduce new languages, especially if
               | they're trying to improve the current system.
        
               | jerf wrote:
               | "it's pretty clear to me. people coming from other
               | languages are used to typing less characters and
               | expressing their ideas like that, and they want to type
               | less characters in go."
               | 
               | It's not. It's more. A lot more sometimes, plus the
               | indentation for trying to chain maps together is ugly.
               | 
               | I think more people who think this is going to be great
               | need to clock some time on the Go Generics playground
               | [1], implement some Map and Filter and MapError
               | functions, and convert some real code to this style in
               | real Go. It's awful. I'm probably spoiled by Haskell and
               | how nice it makes all this, but it's awful even against
               | normal Go code. Try it.
               | 
               | The problem is, the thing that makes "functional-style"
               | programming in Go difficult wasn't "missing generics". It
               | was, missing generics, somewhat heavy-weight anonymous
               | function syntax, indentation rules not optimized for
               | deeply-nested functions, a compiler that almost certainly
               | won't optimize through very many layers of these
               | constructs, the inability to put methods on core types
               | and the fact the core types are missing the useful
               | methods/interfaces like "Traversable", no higher kinded
               | types so no monads or anything else like that, a variety
               | of little syntax issues that make this all slick, and
               | probably some things I'm missing. Just getting some
               | generics (and not having generic methods that can add new
               | generic types hurts this style too) just isn't enough to
               | make it slick. Go would need a _suite_ of changes to make
               | this slick and efficient, not just these generics.
               | 
               | [1]: https://go2goplay.golang.org/
        
             | solosoyokaze wrote:
             | > _There is nothing preventing people from writing their
             | own reduce() in their own utilities package, and I expect a
             | thriving ecosystem of "even-more-functional-constructs"
             | packages to spring up as soon as generics hit stable._
             | 
             | I really hope this doesn't happen because then you have
             | random utility libraries that "infect" projects for what is
             | essentially basic flow control. I would say stuff like this
             | is best supported in the standard lib and not home rolled.
        
           | throwaway894345 wrote:
           | > The first reply to the thread deems "map" and "filter"
           | borderline-useful, but "reduce" not worth it - that type of
           | misunderstading just shows that the author (and the 60+
           | people who +1'd it) didn't spend enough time understading
           | functional constructs yet.
           | 
           | It's not very charitable to assume that people who disagree
           | with you must not understand your position. In particular,
           | one person could like reduce() because it's very abstract and
           | can be used for lots of problems, but critics could dislike
           | it because very high levels of abstraction impede
           | readability. Both parties may understand each other, but they
           | are optimizing for different things (abstraction for its own
           | sake and readability, respectively).
        
             | sabellito wrote:
             | I definitely didn't mean it as an insult, as my comment is
             | a reflex of my own journey understanding these concepts.
             | I'm also absolutely not an FP expert. Different people have
             | different levels of understanding about different things,
             | this has nothing to do with intelligence or a general
             | programming skill level.
             | 
             | I do think this is about understanding, as it's not about
             | syntax, or what feels nicer. It's about building blocks and
             | what comes before what. Given the nature of the proposed
             | library, to give the users an arbitrarily less abstract
             | construct is a design mistake as I mentioned in another
             | comment in this thread.
             | 
             | For example, I remember when I needed to flatten a
             | bidimensional array in JavaScript, perhaps back in 2005,
             | and the internet told me that I needed to flatten the
             | array. So I learned then that array.flatMap() served that
             | purpose: to flatten a bidimensional array. This is of
             | course incorrect, but it took me many years until I found
             | the concept again and understood what were the building
             | blocks around flatMap, and what kinds of other abstractions
             | it allowed.
        
               | throwaway894345 wrote:
               | > I do think this is about understanding, as it's not
               | about syntax, or what feels nicer. It's about building
               | blocks and what comes before what. Given the nature of
               | the proposed library, to give the users an arbitrarily
               | less abstract construct is a design mistake as I
               | mentioned in another comment in this thread.
               | 
               | Per my earlier comment, it's only "a design mistake" if
               | you assume that it's always better to trade readability
               | for abstraction. This isn't always the case, and in
               | professional software engineering it's often better to
               | optimize for readability--rarely are high degrees of
               | abstraction beneficial (of course, someone will respond
               | to this with some anecdotes of the instances in which
               | high degrees of abstraction are helpful, which is just an
               | enumeration fallacy).
        
           | kubb wrote:
           | Bingo, Go is geared towards junior programmers. Somebody who
           | exits the university has to be able to feel productive within
           | two weeks of using it. That's literally the cornerstone of
           | the entire design and all things follow from it (C-like
           | syntax, attempt to minimize the number of language
           | constructs, and the heavy focus on imperative programming
           | which is what they teach in college).
        
             | cy_hauser wrote:
             | Oh the unintended irony. Go's syntax may be simple enough
             | to pick up in a few weeks but writing "good" Go code turns
             | out to be much much harder. Idiomatic Go can take a year or
             | two to internalize and introducing non idiomatic Go code
             | into an established code base can really muck it up.
             | 
             | You can see this clearly in job listings looking for Go
             | coders. Almost nobody is looking for junior level Go coders
             | with less than a year of experience. Companies are looking
             | for Go coders with at 2, 3, 5+ years of experience instead.
             | 
             | A simple syntax may provide benefits, but it doesn't seem
             | to be the benefits most companies want in practice after
             | all.
        
               | brandonmenc wrote:
               | > Almost nobody is looking for junior level Go coders
               | with less than a year of experience.
               | 
               | I have _never_ seen a job posting asking for less than a
               | year of experience with _anything_.
        
       | joelfolksy wrote:
       | Sigh. Yes, tell us more about why the style you've written 3-4
       | _orders of magnitude_ more code in is inherently simpler
       | /clearer.
        
       | parhamn wrote:
       | I can't wait to see what comes out of the channels/goroutine
       | generic packages! Given go's culture it would be really
       | interesting what they deem should make the cut and what
       | shouldn't.
        
       | stevebmark wrote:
       | My biggest surprises learning Go were that it didn't have
       | generics, and that it didn't have native map/reduce/filter. I
       | think of many programs in terms of these three constructs and
       | combining them.
        
         | int_19h wrote:
         | It's interesting that some of the comments on this issue are
         | complaining that HOFs are non-Go-like, and that if adopted,
         | they will "take over" for-loops, which would be undesirable.
        
         | skywhopper wrote:
         | Yep, different languages have different idiomatic styles, for
         | lots of reasons. When I'm learning a new language that's very
         | different, I have found that breaking through these mental
         | roadblocks opens my mind to a lot of new possibilities and
         | considerations. No language can be everything to all people and
         | purposes.
        
       | kubb wrote:
       | Luckily, with the design of generics as it is now, we'll be able
       | to add the parts that are missing from this proposal ourselves in
       | custom libraries. And even if the Go team doesn't implement
       | generating efficient machine code for higher order functions, it
       | will only matter in the 1% of the codebase that is performance
       | critical, and I will gladly switch that to a loop.
        
       | mutatio wrote:
       | The generic slice functions would certainly reduce boiler plate,
       | but what I miss in comparison to Rust is the concept of
       | iterators. In Rust if you were to iterate over words in a string
       | you'd get borrowed words, so in practice lots of things are non-
       | allocating (unless you choose to collect / take ownership) - in
       | Go etc. instead of windowing over a string you typically fully
       | allocate the []string result (e.g. strings.Split /
       | strings.Fields). Sub-slicing in Go can be used to make your own
       | iterators, but the ergonomics take a hit.
        
         | eloff wrote:
         | Rust iterators are beautiful. If you've ever implemented C++
         | iterators, you'll be pleasantly surprised at how elegant it is
         | in Rust by comparison (actually you could replace iterators
         | with about any language feature and the above holds true.)
         | 
         | I really like Rust as a language, it's like a lightsaber, an
         | elegant weapon from a more civilized era. Go feels a bit more
         | like a blaster (sorry May 4th was yesterday.)
         | 
         | That said, when it comes to getting things done, the blaster
         | seems more effective. I would chalk that up to garbage
         | collection, goroutines vs async+await, and simplicity of the
         | language in general. That said I use both languages regularly
         | because they have different strengths and weaknesses and one is
         | usually a clearly better option depending on the problem and
         | requirements.
        
           | biztos wrote:
           | > the blaster seems more effective
           | 
           | And you have to, er, attack the rebels with an army of highly
           | variable talent and training, which contains maybe one or two
           | actual Jedi Ninjas, so maybe it's better if everyone just
           | uses blasters and nobody cuts off their own arm.
           | 
           | I'd love to work in some super elegant language for my own
           | intellectual betterment, but I really hope my next real-world
           | gig uses something like Golang if there are more than a half-
           | dozen devs... ever.
        
             | throwaway894345 wrote:
             | Agreed. I love Rust for my personal projects, but I
             | wouldn't trust colleagues with it at any place I've worked.
             | And it's not that my colleagues aren't intelligent or
             | capable, but that Rust's expressiveness invites
             | bineshedding on the right amount of abstraction or which
             | crate to use or the right way to do errors or how to
             | configure rustfmt or when to just give up a fight with the
             | borrow checker and use clone() or etc.
             | 
             | Rust is a really, _really_ cool language--perhaps my
             | favorite language, but I've worked in C++ shops and Python
             | shops and I fully expect the same amount of bike shedding
             | at a Rust shop.
        
           | obviouslynotme wrote:
           | Rust is a beautiful language. Its only issue is that
           | programming in it is painful for anyone who hasn't followed
           | Modern C++ for a decade and then read everything on Rust,
           | which is just Modern C++ with some niceness from ML thrown on
           | top.
           | 
           | I couldn't even begin to imagine onboarding a Jr Engineer to
           | a complex Rust project. I don't even know if it is possible.
        
           | mrweasel wrote:
           | Apparently I'm pretty much alone in thinking that Rust is a
           | clumbsy, messy language. The syntax is all over the place.
           | You get the feeling that there's no design strategy and than
           | anyone with half an idea is allowed to bolt on features.
        
             | jolux wrote:
             | Rust's syntax is definitely complicated and that's one of
             | my least favorite things about it but it's extremely
             | consistent. What gives you the "all over the place"
             | feeling?
        
               | mrweasel wrote:
               | I don't really agree that it consistent, but maybe you're
               | right that it just complicated. There are so many
               | different syntaxes for all sorts of features that you
               | can't really hold it all in your head.
               | 
               | It's been a year since I last gave Rust a try, in the
               | mean time I've been able to pick Go without much effort.
               | Rust just seem like PHP, it's fast, featurefull but not
               | thought through.
        
               | nindalf wrote:
               | You're assuming that it was difficult to learn because
               | the features weren't thought through. Have you considered
               | that maybe it was because there were genuinely new
               | concepts not present in other languages?
        
               | stouset wrote:
               | Yeah, if anything, Rust feels to me like it's incredibly
               | well thought through. All the pieces fit together well
               | precisely as if they were all thought of as a whole. It's
               | "hard" because it forces you to consider an entirely new
               | constraint of your programs upfront. But (almost) every
               | piece composes nicely with every other piece in a way
               | that makes Rust development extremely pleasant.
               | 
               | Golang feels like the opposite, in that none of the parts
               | are actually built to work with one another. Tuple
               | returns exist but there are no language features that
               | allow you to actually work with them. There's a strong
               | type system, but you have to break out of it with
               | interface{} and type punning to solve entire classes of
               | common problems. Type punning exists, but go can't prove
               | exhaustiveness of switch statements so doing this opens
               | further gaping holes. Errors are handled explicitly
               | everywhere, but again there are no tools to handle the
               | repetitive lifting so every program becomes an endless
               | series of error handling occasionally interrupted by bits
               | of program logic. And let's not forget how badly
               | interface pointers interact with nils: comparing a nil
               | interface pointer against nil returns false, so a basic
               | nil-check that works everywhere else fails to actually
               | perform correctly when dealing with interfaces.
        
             | steveklabnik wrote:
             | > anyone with half an idea is allowed to bolt on features.
             | 
             | I know you're talking about _feel_ here, not about process,
             | but to be clear, while we do have a pretty open and fairly
             | democratic process, and while anyone _can_ propose a
             | feature, only the language team members, which is currently
             | five folks, can decide to say  "yes" to a proposal. The
             | team may have been plus or minus a few over the years, but
             | it's never been particularly large.
             | 
             | (We generally feel this process works well to balance all
             | these factors; we want everyone's good ideas, but we also
             | have to maintain coherence. An open suggestion process with
             | a closed selection process has worked well in our eyes, but
             | everyone can feel differently, of course.)
        
             | derefr wrote:
             | I'm guessing you've looked more at Rust code that uses
             | incubating features from Rust nightly.
             | 
             | Most of those features don't make it to stable--or if they
             | do, their syntax has usually been refined by the time they
             | do.
             | 
             | Inconveniently, a lot of the Rust code examples you'll run
             | into on HN uses incubating features, because a lot of the
             | people posting about Rust on HN are language researchers
             | talking about how they were finally able to implement new
             | language feature/performance optimization/safety guarantee
             | X using incubating language feature Y; or how they think
             | nascent library Foo's use of incubating language feature Z
             | is/isn't a good idea.
             | 
             | All that is inside baseball. It looks nothing like day-to-
             | day Rust code, any more than e.g. .NET CLR static analysis
             | code looks like day-to-day C#.
             | 
             | (Also, there's the code examples of Rust stdlib code, which
             | is like C++ STL code in that you have to use arcane
             | language features "in anger" to bootstrap the language --
             | e.g. safe concurrent data structures must necessarily at
             | some point rely on unsafe code.)
             | 
             | (Or it's code for a Rust unikernel / OS device driver /
             | etc, because that's a thing you can do. But it's a _still-
             | nascent_ thing you can do, that's not _yet_ as ergonomic as
             | regular code that can rely on virtual memory to exist. You
             | have to build the safety yourself in such cases, or rely on
             | a library that does; and there are no such libraries that
             | have yet reached high code quality + wide cross-platform
             | applicability.)
        
               | nyanpasu64 wrote:
               | Rust stdlib code is a _lot_ nicer to read than C++ stdlib
               | code, which relies on _Mangled names to prevent
               | conflicts, and (ab)uses template metaprogramming, SFINAE,
               | overloading,  & and && and forwarding, etc. heavily.
               | Though Rust's trait system results in indirections
               | comparable to (but somewhat more manageable than) C++
               | SFINAE/overloading/type traits. And some Rust stdlib
               | types delegate to compiler intrinsics rather than being
               | implemented in unsafe Rust (forgot which).
               | 
               | Additionally, third-party Rust data structure/concurrency
               | crates often contain unsafe code comparable to Rust's
               | stdlib in complexity. (And device drivers, as you
               | mentioned, are probably highly unsafe too.)
        
             | onei wrote:
             | Is that because Rust is an expression language? As much as
             | I like Rust, I do find that returning variables from
             | conditionals or match expressions can feel a little jarring
             | (especially when you combine the two). In other places
             | though, it really shines and just requires a little
             | discipline.
        
           | alexhutcheson wrote:
           | > If you've ever implemented C++ iterators, you'll be
           | pleasantly surprised at how elegant it is in Rust by
           | comparison
           | 
           | You probably already know this, but if you're willing to take
           | a dependency on Boost.Iterator, then
           | boost::iterator_facade[1] makes it much easier and less
           | error-prone to implement a standards-conforming iterator, and
           | the pre-built iterator adapters[2] cover lots of common
           | cases, so you don't need to directly implement an iterator at
           | all.
           | 
           | Not arguing the relative merits of C++ vs. Rust, just leaving
           | a pointer here for anyone who didn't know about it.
           | 
           | [1] https://www.boost.org/doc/libs/1_66_0/libs/iterator/doc/i
           | ter...
           | 
           | [2] https://www.boost.org/doc/libs/1_66_0/libs/iterator/doc/i
           | nde...
        
           | throwaway894345 wrote:
           | I really admire Rust iterators, especially compared to C++
           | iterators, but I lament that there is no easy way (that I
           | know of, anyway) to build an iterator from a closure which
           | yields `Option<T>`s. I don't like that I have to define a
           | type and implement a Trait (in general working with closures
           | is cumbersome in Rust). That said, this isn't a major problem
           | either and on balance I'm very happy with Rust iterators.
        
             | eloff wrote:
             | It's probably possible to create a wrapper to do that. But
             | if you need a closure, you can also just use a struct
             | implementing the trait to manage the state. It's not
             | perfect, but it's so much nicer than in C++.
        
             | creata wrote:
             | Might you be looking for https://doc.rust-
             | lang.org/std/iter/fn.from_fn.html ?
        
               | throwaway894345 wrote:
               | Oh wow, yeah, I think that's it! Thanks very much!
        
               | forrestthewoods wrote:
               | Whoa I did not know about this. This would have saved me
               | so much time during Advent of Code...
        
               | eloff wrote:
               | Thanks for sharing! I wish I'd known about this earlier.
        
             | masklinn wrote:
             | > I lament that there is no easy way (that I know of,
             | anyway) to build an iterator from a closure which yields
             | `Option<T>`s.
             | 
             | Could you expand / explain what you actually mean here?
             | Because I can think of multiple interpretations of this
             | prompt which are straight up part of the stdlib, but that I
             | can interpret this description in multiple ways shows the
             | issue.
        
               | throwaway894345 wrote:
               | Let's say I have a closure `fn() -> Option<T>`--how do I
               | turn that into an Iterator? Maybe it's possible with the
               | stdlib and I'm just not familiar?
               | 
               | EDIT: I think this is it:
               | https://news.ycombinator.com/item?id=27050861
        
               | kzrdude wrote:
               | Let's say std::iter::from_fn didn't exist. Then you'd
               | need to make something that ends the iterator when the
               | function returns none, you could do it like this:
               | std::iter::repeat_with(f).take_while(|elt|
               | elt.is_some()).filter_map(|elt| elt)
               | 
               | Where `.filter_map()` removes the Option layer and
               | `repeat_with` creates an initially infinite iterator from
               | your function `f`.
               | 
               | Long time ago, `repeat_with` might not have existed.
               | Maybe you'd do something like this instead for that in
               | Rust 1.0.0: `std::iter::repeat(()).map(|_| f())...`
               | 
               | You prefer to use std::iter::from_fn now that it exists,
               | of course!
               | 
               | To be honest, making a simple iterator adaptor is quite
               | easy too, so you could have a knack at making one you are
               | missing - if you can't find it in std or in the crate
               | itertools already.
        
         | iib wrote:
         | Yes, iterators are typically a hard[1] problem in Go. I think
         | this essay[2] describes perhaps the closest lazy eval iterator
         | pattern in Go, which is much more complex to write than
         | languages like Python.
         | 
         | [1] https://ewencp.org/blog/golang-iterators/index.html [2013]
         | 
         | [2] www.catb.org/~esr/reposurgeon/GoNotes.html [2020]
        
           | throwaway894345 wrote:
           | I don't understand what's hard about iterators in Go after
           | reading that article. I've written a lot of Python and Go (15
           | and 10 years respectively) as well as a bit of Rust and while
           | iterators are certainly simpler in Python and Rust since Go
           | lacks generics, most of the "challenges" presented in the
           | article seem petty (complaints about for loops). The easiest
           | way to write an iterator in Go is the closure/generator
           | method and you can use it in a for loop like so:
           | for x := next(); x != nil; x = next() { ... }
           | 
           | Not as nice as a for/range but not a real problem. With
           | generics we can have libraries that operate on iterators like
           | map, filter, reduce although I also think people make way too
           | big a deal about a tiny bit of for loop boilerplate (there
           | are legitimate reasons for generics but I don't think small
           | amounts of boilerplate are among them).
        
           | johnmaguire wrote:
           | Clickable: http://www.catb.org/~esr/reposurgeon/GoNotes.html
        
           | psanford wrote:
           | There's nothing hard about writing iterators in go.
        
         | edflsafoiewq wrote:
         | Rust iterators are hard to implement without a big compile time
         | hit.
        
           | uh_uh wrote:
           | Compile time hit meaning it's difficult to get the types
           | right or something else?
        
             | runeks wrote:
             | Presumably that it's inherently slow to compile. "Getting
             | the types right" happens before compile time.
        
             | masklinn wrote:
             | That they rely on a lot of optimisations to be really
             | efficient (= have any chance of approaching a regular for
             | loop).
             | 
             | Without that, every adapter adds a function call to the
             | yielding of an element.
        
         | topspin wrote:
         | What I miss in comparison to Rust is concise error handling.
         | Rust has shown that it is entirely possible to have clear, type
         | safe, concise error handling without try/catch rigoromol or
         | if(err == nil) {...} pollution. My hope is that the
         | introduction of generics in Go eventually leads to Result[]
         | proliferating throughout golang.
        
       ___________________________________________________________________
       (page generated 2021-05-05 23:01 UTC)