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