[HN Gopher] Notes on the Go translation of Reposurgeon (2020)
       ___________________________________________________________________
        
       Notes on the Go translation of Reposurgeon (2020)
        
       Author : spekcular
       Score  : 110 points
       Date   : 2021-12-09 05:25 UTC (1 days ago)
        
 (HTM) web link (gitlab.com)
 (TXT) w3m dump (gitlab.com)
        
       | wpietri wrote:
       | What a delight to read, even as somebody who has barely used Go.
       | I really appreciate the author's self-awareness as demonstrated
       | in things like "Expected problems that weren't".
       | 
       | The bits about exceptions and typing remind me of an open
       | question I have about Go: to me it mainly looks like a language
       | for well-understood problems. Static typing and the lack of
       | ability to do broad, high-level exception catching seem to me to
       | be well-matched for problems where you already understand the
       | solution pretty well. E.g., a port like this.
       | 
       | But how is it for poorly understood problems? E.g., You're doing
       | a startup where the technical risks are low and the major
       | unknowns are about user needs and the correct experiences to
       | deliver. Or you're doing exploratory work for an art piece.
       | 
       | In those contexts, I've felt much more effective starting with
       | vague/implicit typing and some broad, high-level exception-
       | catching blocks. That lets me avoid a bunch of questions about
       | the right types and structures until a more solid domain
       | vocabulary emerges. At that point you can refactor/rewrite toward
       | clarity. But I don't see a good way to be willfully vague/casual
       | in Go.
        
         | autarch wrote:
         | I feel like strong typing _helps_ when exploring a new domain,
         | as it forces you to really think about the data you're
         | operating on. In a language like Perl or Python, it's so easy
         | to just throw around a bunch of hashes/dicts and that can get
         | messy really quickly.
         | 
         | I don't miss _exceptions_ in Go but after using Rust for a
         | while I've come to really love Rust's `Option` and `Result`
         | types. They're more ergonomic and expressive than Go's errors-
         | as-values, and the use `Option` also eliminate `nil` entirely,
         | which has been a regular source of runtime panics in every
         | large Go codebase I've worked on.
        
           | tialaramex wrote:
           | Sum types are a thing I don't want to be without again. Not
           | only the built in Option and Result, but once you're in the
           | mindset you find they're the right fit for other problems
           | too.
           | 
           | In Rust itself, writing stuff where I actually care whether
           | my structure is 16 or 20 bytes because I need to fit hundreds
           | of millions of them into RAM, I like that Sum types ensure
           | Option<NonZeroUsize> is the same size as usize, by reasoning
           | that 0 isn't a valid NonZeroUsize and so it can signal None.
           | However on a language like Go I don't miss that - what I do
           | miss is the fact that in Rust I can't mistakenly end up with
           | None(actually_something) or both an error and the result that
           | shouldn't be there if there was an error.
        
           | kraig wrote:
           | With generics around the corner I am really hoping we see
           | some flavor of Option & Result coming to Go. But I won't hold
           | my breath for ?.
        
             | preseinger wrote:
             | > I won't hold my breath for ?.
             | 
             | Good idea :) `?` hides a control flow that Go takes great
             | pains to make explicit. Adding it to the language would be
             | a disaster.
        
           | teddyh wrote:
           | Python _is_ strongly typed, not weakly typed. What you are
           | talking about is _static_ typing, which Python is not; Python
           | is _dynamically_ typed. Please do not confuse the two
           | concepts.
        
         | marcus_holmes wrote:
         | Honestly, you don't notice it once you get into the Go mindset.
         | 
         | The exception handling looks onerous, and is a pain at the
         | start. But it doesn't take long to get used to it, and then
         | (for me anyway) it becomes second-nature. Everything returns an
         | error, and you have to handle that error (even if only passing
         | it up again).
         | 
         | Static typing is more "fun" when you're exploring new concepts,
         | but interfaces are the key. Defining the interface at the
         | beginning ("what do I need this thing to do?") and then
         | implementing it with a struct/whatever, feels like the right
         | way to do this.
        
           | oefrha wrote:
           | > But it doesn't take long to get used to it
           | 
           | I did get used to it, but all the                 if err !=
           | nil {         return nil, err       }
           | 
           | sometimes taking up half or more of the vertical space of a
           | function is still an eyesore to me, even after writing a
           | quite substantial amount of Go.
        
             | preseinger wrote:
             | Error handling being explicit isn't an eyesore. It lifts
             | what is with exceptions a hidden control flow to the
             | foreground.
        
             | ufmace wrote:
             | This exactly. I haven't written enough Go to be really
             | annoyed by the more subtle things in the post, but it'd
             | really be nice if they could come up with some syntax sugar
             | for that kind of like Rust's ? helper.
             | 
             | Maybe the Go ? is allowed within any function whose last
             | return value is an error on any function call whose last
             | return value is also an error. Call it with ? at the end
             | and accept all but the last return value. At runtime, if
             | the err is not nil, then it returns from the function,
             | supplying the err value and nil for any other return
             | values.
        
               | oefrha wrote:
               | https://go.googlesource.com/proposal/+/master/design/go2d
               | raf...
               | 
               | I don't think it went anywhere, unless I missed it.
        
           | wpietri wrote:
           | Not noticing it is exactly my fear.
           | 
           | Passing an error manually up several levels when it may only
           | be a theoretical concern is a ton of expressive duplication.
           | If I'm in the mindset, that would feel like valuable work,
           | even though it isn't actually making things better for users.
           | 
           | It reminds me of one team I dealt with years ago. They were
           | Java experts used to doing enterprise stuff; their overlords
           | had put them on a scrappy, startup-like thing that was
           | intended to be open source. As was in fashion for Java at the
           | time, they had written things in many layers, the goal of
           | which was to provide scaling cut-points. However valuable in
           | theory, the project ever needing to scale was uncertain. What
           | really mattered was finding something that served user needs.
           | But the layer-by-layer duplication increased the cost of
           | change significantly, lowering the odds we'd find the right
           | product.
        
             | preseinger wrote:
             | What you call "expressive duplication" I call "explicit
             | intent". The error control flow is not subordinate to the
             | happy path.
        
         | whoisjohnkid wrote:
         | All depends on what's important to you in that startup context.
         | If you need a good mix of high productivity and low costs on
         | the server side I think go is a good fit.
         | 
         | If you don't care so much about costs and scale on the server
         | side then something like Ruby on Rails might give you that
         | extra productivity boost you need to verify the concept.
        
           | wpietri wrote:
           | Yeah, given how computers keep getting cheaper, I only start
           | caring about low server costs once I have a demonstrated cost
           | problem that's going to be material to the business model or
           | to the budget. Until then I'm very happy to burn CPU time to
           | accelerate developers. Who keep getting more expensive!
           | 
           | And yes, the point of verifying the concept is key for me in
           | this. Before product-market fit, I just don't have much
           | confidence in any domain model. Once we have demonstrated
           | that particular people are excited to pay for a particular
           | thing, that changes. Then we can understand how those people
           | think, and how we think about their behavior, such that we
           | can do real domain-driven design. At that point I'm much more
           | willing to lock down types.
        
         | laumars wrote:
         | I've never once felt like strong typing has prevented me from
         | solving unknown problems. I won't deny that there have been
         | occasions where I've had to redefine a type across a large code
         | base and that has taken me half an hour to a hour. But I'd take
         | that over type validation at run time.
         | 
         | For me, having strict typing helps with unknown problems
         | because it gives me greater confidence that refactoring
         | wouldn't introduce subtle regression bugs.
        
           | wpietri wrote:
           | Thanks for the reply.
           | 
           | For me the cost isn't just _re_ defining a type. It's the
           | continuous work of reifying types that could be implicit for
           | a while and possibly forever, because concepts don't last
           | long enough to need it.
           | 
           | For me, having written a lot of code in Java and Scala but
           | also in Ruby and Python, typing is a nice adjunct to unit
           | tests and operational monitoring, but not my fundamental way
           | of ensuring correctness. And correctness often isn't the
           | highest value in practice, especially early on in a startup's
           | life.
        
       | kubb wrote:
       | > What I really missed was generic map-function-over-slice
       | 
       | Me too, and mapping a map, a channel, etc. With generics, things
       | will be easier, we can have iterators, even lazy iterators, but
       | there will still be no type inference in lambdas for arguments
       | and return values and no easy currying, so things will still be
       | awkward, but better.
       | 
       | > Keyword arguments
       | 
       | You can kind of do that by passing a struct as an argument, and
       | represent the optionalness by having a pointer in the struct.
       | That's still awkward, you can't easily make a pointer to an
       | integer or string literal, you need a separate helper function
       | for each primitive type. Overall it could be nicer.
       | 
       | > Annoying limitations on const
       | 
       | I think the Go constants are good, the fact that you can perform
       | calculations on them in compile time also. But I wish there was
       | some way to express immutability (and non-nilability) in the
       | language. Programs are simpler with less moving parts.
       | 
       | > 14KLOC -> 21KLOC
       | 
       | That's to be expected, Go could be nicer if it was more concise,
       | obviously that's a hard thing to balance, because you can end up
       | with too obscure code, I just wish that there were some small
       | improvements from time to time.
       | 
       | > Absence of sum/discriminated-union types
       | 
       | Yeah, I refer to it as Algebraic Data Types, where you can for
       | example have your tree type to be a Node with children or a Leaf
       | with value. And a function can accept a Tree that can be one of
       | these. IIRC, the Go people claim that you can do kind of
       | something like this with interfaces, but it's not very nice to
       | use interfaces in this way and has some drawbacks.
       | 
       | > Catchable exceptions require silly contortions
       | 
       | I don't like exceptions, they disrupt the flow of the program -
       | any function call could "return early", so you need to be always
       | careful and account for that possibility. In C++ this was solved
       | with RAAI, and it was a source of so many bugs that some
       | companies just disallow exceptions internally.
       | 
       | > Aesthetic doubt
       | 
       | Yeah, it's there, some things in Go just come out ugly.
       | 
       | > Absence of iterators
       | 
       | Yes. Hopefully generics will enable implementing them.
        
         | autarch wrote:
         | > _You can kind of do that by passing a struct as an argument,
         | and represent the optionalness by having a pointer in the
         | struct. That 's still awkward, you can't easily make a pointer
         | to an integer or string literal, you need a separate helper
         | function for each primitive type. Overall it could be nicer._
         | 
         | I think using a builder is a better option in Go if you have
         | more than 2-3 arguments.
         | 
         | > Yeah, I refer to it as Algebraic Data Types, where you can
         | for example have your tree type to be a Node with children or a
         | Leaf with value. And a function can accept a Tree that can be
         | one of these. IIRC, the Go people claim that you can do kind of
         | something like this with interfaces, but it's not very nice to
         | use interfaces in this way and has some drawbacks.
         | 
         | Except you can't really do this with interfaces. If you need to
         | get back to the concrete implementation of the interface,
         | there's no way to know what all possible implementations might
         | be. This is especially true since any type can implement an
         | interface, not just the types you create initially. So someone
         | else could implement `Tree` and you would not know to account
         | for that in your function that accepts the `Tree` interface.
         | 
         | I don't think there's any substitute for proper sum types in
         | Go.
        
           | kubb wrote:
           | > I think using a builder is a better option in Go if you
           | have more than 2-3 arguments.
           | 
           | But then you need to write all the setters, and something
           | that was supposed to be simple, a function, becomes a whole
           | contraption with a bunch of auxiliary code. Personally I
           | would avoid that, and I definitely wouldn't make it a rule of
           | thumb to use builders in every function with more than 2-3
           | arguments (maybe you meant something else).
        
             | autarch wrote:
             | > _But then you need to write all the setters, and
             | something that was supposed to be simple, a function,
             | becomes a whole contraption with a bunch of auxiliary code.
             | Personally I would avoid that, and I definitely wouldn 't
             | make it a rule of thumb to use builders in every function
             | with more than 2-3 arguments (maybe you meant something
             | else)._
             | 
             | Yeah, it's tedious, but that's Go for you ;)
             | 
             | Rust has the same issue, but there are macros libraries
             | that will create the builder for you based on the struct
             | definition, so it's _very_ trivial to create these
             | builders.
             | 
             | You could do the same with codegen for Go, but this always
             | feels much worse to me than using macros for a number of
             | reasons like having to install a separate tool, checking in
             | generated files, making people run `go generate` after some
             | (but not all) changes, etc.
        
           | preseinger wrote:
           | Builders aren't a good pattern in Go, because it's difficult
           | to express continuations, and they leave types in incomplete
           | states. It's almost always better to use config structs.
        
           | kubb wrote:
           | > Except you can't really do this with interfaces
           | 
           | Apparently they decided that it would be too confusing to
           | have variant types alongside interfaces for some reason, and
           | they claim that interfaces handle a lot of the use cases of
           | variants:
           | 
           | https://go.dev/doc/faq#variant_types
        
             | autarch wrote:
             | Yeah, "for some reason" is a good summary of many Go
             | decisions.
             | 
             | Rust has both (traits are somewhat like interfaces) and I
             | don't feel like it's too painful, though using the trait
             | type in function signatures is a lot more involved than in
             | Go, and I still don't fully understand all the nuances. But
             | the complexity isn't because of any overlap between traits
             | and sum types.
        
               | throwaway894345 wrote:
               | Rust trait objects are surprisingly painful. I've reached
               | for them a lot where I would normally use Go interfaces,
               | but ended up avoiding them. I suspect this has more to do
               | with Rust's memory management model--having sum types and
               | interfaces would almost certainly work out perfectly in
               | Go.
        
       | whoisjohnkid wrote:
       | Raphael Poss did a really insightful talk on how to gracefully
       | panic[0]; this gives try catch like functionality, though should
       | be used sparingly IMO.
       | 
       | - [0] https://m.youtube.com/watch?v=c78U0MZ4b_c
        
       | JulianMorrison wrote:
       | FWIW, Go _does_ have keyword struct members, so it's possible to
       | do                   type A struct {           B int           C
       | int         }              myfunc(A{B: 1, C: 2})
        
         | throwaway894345 wrote:
         | I do this a lot. You can even do `A{B: 1, C: 2}.myfunc()` in
         | many cases.
        
       | nicoburns wrote:
       | The article mentions that Go, OCaml, or a compiled lisp were
       | considered for this project. I wonder why Rust wasn't on that
       | list. It seems to cover every concern that is raised here except
       | for keyword arguments (which are high on my list of desired Rust
       | features too).
       | 
       | I guess maybe they were worried about the lack of GC, but my
       | experience has been that's it's generally quite easy to port code
       | from dynamic languages like Python/JS/PHP to Rust. So I suspect
       | that may have gone into their "Expected problems that weren't"
       | section if they'd tried it.
        
         | eikenberry wrote:
         | Go, OCaml and Lisp (depending on the Lisp) all have one other
         | thing in common.. a fast compile/feedback loop. Rust doesn't
         | have a good story here. This would be my guess as to why (at
         | least in part). Particularly coming from a dynamic languages
         | which have super fast compile times if they compile at all.
        
         | db48x wrote:
         | Async wasn't in yet at the time. He did investigate Rust to a
         | certain extent, but bounced hard. His chosen program didn't
         | really show off Rust's strengths, because he started by trying
         | to call select and write essentially the same program that he
         | would have written in C. That's a pretty painful way to go.
         | 
         | Incidentally, I helped with the port to Go, and I spent some
         | time polishing the code, and finding and fixing performance
         | problems. When we were helping GCC convert their SVN repository
         | to Git (a repository with 287k commits, btw), we reduced the
         | memory usage by 50% (from over 250GB to under 128GB), and the
         | run time by quite a lot as well (down to just around 2 hours to
         | read in the SVN repository and convert it to a basic Git
         | repository).
         | 
         | Now that we've done that work, Reposurgeon spends 50-60% of its
         | cpu time scanning the heap for garbage. There is often garbage
         | to find, but just as often there is not. GC is useful, but for
         | Reposurgeon it has become a bottleneck.
         | 
         | My preliminary work on a Rust port shows that it is around 4x
         | faster than the Go version. I personally think that Rust is the
         | future, but I haven't been able to put as much effort into the
         | port as I would like.
        
           | stouset wrote:
           | Appreciate your thoughtful reply! One question. Regarding
           | this bit:
           | 
           | > He did investigate Rust to a certain extent, but bounced
           | hard. His chosen program didn't really show off Rust's
           | strengths, because he started by trying to call select and
           | write essentially the same program that he would have written
           | in C. That's a pretty painful way to go.
           | 
           | Is this stating that he was writing the Rust version
           | similarly to how he would have written it in C _because that
           | was the most natural way to do it in Rust_ or simply because
           | it was the first path he went down? In other words, was your
           | point that Rust is a fundamentally poor fit for this problem,
           | or was it that his lack of experience with the language took
           | him down the wrong path?
           | 
           | Thanks!
        
             | db48x wrote:
             | He went down the wrong path, but at the same time Rust
             | didn't do anything to make async code easier to write. You
             | just had to call libc::select and std::thread::spawn and
             | whatnot, with all of the unsafe blocks that implies. These
             | days there are about 47 different crates that can help you
             | do it, all of them a lot nicer than that.
        
               | stouset wrote:
               | That's what I thought, but I just wanted to confirm.
               | Thanks!
        
               | db48x wrote:
               | You're welcome.
        
         | mwcampbell wrote:
         | Here is what esr wrote about Rust in early 2017:
         | 
         | http://esr.ibiblio.org/?p=7294
         | 
         | http://esr.ibiblio.org/?p=7303
        
           | brandmeyer wrote:
           | > A lot of Rustaceans don't seem to grasp why, when the
           | question is "where do I get feature X?" the answer "oh, there
           | are 23 crates for that" is objectively terrifying.
           | 
           | This is exactly the impression I have gotten from the crates
           | system every time I've looked. It happens for things as
           | foundational as mmap. Also terrifying: The plethora of
           | highly-recommended crates that have not yet committed to a
           | stable API (ie, are still semantically on version 0.X).
        
             | db48x wrote:
             | I'm not sure that's a great example. mmap(2) is a swiss
             | army knife of a function. There are many crates that build
             | all kinds of things on top of mmap, and they're not all
             | interchangeable. Allocators, file io, gpio, actual virtual
             | memory mappings.
             | 
             | My view is that when you develop a product, you have to own
             | everything. The users don't care if the bug comes from code
             | you wrote, or code in a library, or the language's standard
             | library, or the OS. You have to fix the bug no matter what
             | caused it. It doesn't matter if the code came from a vendor
             | or a language designer or stack overflow; you are the one
             | responsible for fixing it if something goes wrong. Non est
             | salvatori salvator, etc.
             | 
             | With that perspective, I don't think that 23 crates is
             | terrifying. I'm going to look them all over and either pick
             | one, or write the 24th crate myself. The result is the same
             | either way.
             | 
             | I do agree with you about APIs though. It is nice to find a
             | crate where the author has had the confidence to stabilize
             | their API and declare the version to be 1.x instead of 0.x.
             | But at the same time I recognize that getting to that point
             | requires some real software to use the crate, to create the
             | feedback loop. If nobody ever used a 0.x crate, no crate
             | would ever get that feedback.
        
               | simion314 wrote:
               | >My view is that when you develop a product, you have to
               | own everything. The users don't care if the bug comes
               | from code you wrote, or code in a library, or the
               | language's standard library, or the OS. You have to fix
               | the bug no matter what caused it. It doesn't matter if
               | the code came from a vendor or a language designer or
               | stack overflow; you are the one responsible for fixing it
               | if something goes wrong.
               | 
               | So then using a programing language that has a big
               | standard library and a more rich and stable ecosystem is
               | a big advantage. If I want to do a network request, parse
               | a json file, parse or format a date or some other trivial
               | thing I prefer to have a good enough built in way to do
               | it then evaluate 15 packages or write it myself.
               | Otherwise you get in a shitty situation where you inherit
               | some projects and it has 20 dependencies with
               | vulnerabilities, 20 dependencies abandoned, some
               | dependencies that are incompatible with some new version
               | of the language/ecosystem causing issue if you would like
               | to upgrade.
        
               | db48x wrote:
               | Sometimes. Using someone else's code is only a benefit if
               | it saves time overall, compared to writing it yourself.
               | You have to count both the time to write it and the time
               | to maintain it over however long your product is active.
               | You have to assume that you'll be maintaining it yourself
               | either way.
               | 
               | Also, don't forget that languages with big standard
               | libraries, like Python or C# or Java, always end up with
               | oodles of stuff in there that is deprecated or
               | incompatible. That's as bad a sign as anything.
               | 
               | But I do agree with you, a lot of the time you have
               | inherited the mess rather than writing it yourself. Not
               | much you can do about it, except to clean it up and then
               | dodge better next time. Going back to Rust specifically,
               | Cargo gives you some tools that make the mess easier to
               | clean. The Rust language also helps, because you can
               | safely use multiple versions of your dependencies, when
               | it turns out to be necessary.
        
               | simion314 wrote:
               | >Also, don't forget that languages with big standard
               | libraries, like Python or C# or Java, always end up with
               | oodles of stuff in there that is deprecated or
               | incompatible. That's as bad a sign as anything.
               | 
               | This does not happen that much, and in Java and .Net
               | world if some functionality is deprecated there is a
               | replacement, I think I only remember some possible unsafe
               | or ineeficient functions were deprecated so you use the
               | better ones. I don't have experience with Rust , only
               | with node/npm and is a hell , for some reason is decided
               | to split stuff in super small pakcagtes of various
               | quality, today I sepnd hours debugging a npm freeze
               | caused by some package , in the end the cause is probably
               | a shit npm/node implementation that crashes when some
               | specific git version is installed , but debugging this I
               | discovered that the unit testing packages the project
               | uses(I inherited them) instead of beeing one or few
               | packages are a few, and for some reason one of them had a
               | weird dependency that it should not have(a dev only
               | despondency ) and this dependency was also just pulling
               | directly from GitHub ...shit in a few years when GitHub
               | is gone lots of things will stop working (I also had
               | issues with scripts because someone changed master into
               | main because American politics...).
               | 
               | I personally prefer the Java or .Net model, a big
               | standard lbirary and then for most important things you
               | have a small number of options since the community did
               | not wanted to do CV driven development, and most of the
               | time you find all you need in a single library with no or
               | few dependencies. But Rust does not have the Java or .Net
               | money to hire devs to work on the boring stuff of
               | correctly implementing standards like json,XML, date&time
               | and keep maintaining it - (not sure how Python did it)
               | and Rust community seems to be inspired byt node
               | community a lot and this is bad.
        
               | brandmeyer wrote:
               | > With that perspective, I don't think that 23 crates is
               | terrifying. I'm going to look them all over and either
               | pick one, or write the 24th crate myself. The result is
               | the same either way.
               | 
               | In a fine-grained ecosystem, the problem is transitive.
               | You aren't just picking one from 20+ for each of your
               | direct dependencies, you are also trusting that they in
               | turn did just as much diligence as you did in picking
               | their direct dependencies and so on. Curation and pruning
               | would at least help mitigate the scope of the problem.
               | 
               | > ... or write the 24th crate myself.
               | 
               | This is a good example of the xkcd joke about standards
               | proliferation. If I found myself in this situation, I'd
               | prefer to keep the 24th private to my own package. DRY
               | has its limits. Sometimes you've just got to specialize.
               | 
               | I'm aware of the irony in saying this in the context of
               | mmap. But in this case, a thin wrapper that reflects the
               | system call's semantics ought to be available in a common
               | `posix` crate that others may freely rely upon to build
               | their higher-level services. There's no good reason for
               | e.g. ripgrep to have to pick and choose among a dozen
               | flowers for it.
        
               | db48x wrote:
               | > In a fine-grained ecosystem, the problem is transitive.
               | 
               | Absolutely. You definitely have to own the whole stack.
               | Ultimately, you may have to fix bugs at every level, from
               | your own code all the way down to the OS. Big tech
               | companies do this explicitly, with kernel development
               | teams. Small companies do this by occasionally upgrading
               | to the latest version of Ubuntu and hoping for the best.
               | 
               | > But in this case, a thin wrapper that reflects the
               | system call's semantics ought to be available in a common
               | `posix` crate that others may freely rely upon to build
               | their higher-level services.
               | 
               | It is available. If you want to directly call any
               | function in the C standard library, use the libc crate.
               | Here's the documentation:
               | https://docs.rs/libc/0.2.109/libc/fn.mmap.html
               | 
               | All the other crates on crates.io that you find when you
               | search for "mmap" are higher level abstractions over the
               | raw syscall. They have specific purposes, like file io,
               | or creating circular buffers.
        
             | throwaway894345 wrote:
             | I agree, but at the same time things tend to stabilize over
             | time as the community settles around the best packages. The
             | real thing to fear is the relative instability of Rust at
             | the present moment (for certain things, like async), but
             | the overall trajectory seems promising.
        
           | kubb wrote:
           | It sounds that it was hard to learn and use for him, that's
           | why he preferred Go and Python. It's a frequent complaint and
           | the step from Python to Go is indeed easier than from Python
           | to Rust. He also had some philosophical concerns about the
           | crate system.
        
         | steveklabnik wrote:
         | Two threads from back in the day about this:
         | 
         | https://news.ycombinator.com/item?id=13385530
         | 
         | https://news.ycombinator.com/item?id=13398068
        
       | badhombres wrote:
       | Great writeup. Some of the points are moot now that generics are
       | being released, but some very valid concerns.
       | 
       | I would also add that Enums + Exhaustive Switch is a very very
       | weak area in Go that would really benefit the language a ton.
       | I've used those features in other languages and that's one of the
       | things I miss the most, especially when dealing with a ton of web
       | API's that have a defined set of values for properties.
       | 
       | Able to have the confidence that we're checking for every
       | situation that could occur on an enum type across the codebase is
       | one less thing I need to worry about.
        
         | marcus_holmes wrote:
         | Seconded on Enums. It's annoying that they're not there.
        
         | throwaway894345 wrote:
         | Strongly agree here. I'd rather have this than generics,
         | personally. Having to implement my own sum types every time is
         | laborious (moreso than most boilerplate that people complain
         | about) and it's also hard for users to understand (what are all
         | of the permutations?) and extend (I added a new permutation;
         | where are all of the sites I need to update?).
        
         | tjalfi wrote:
         | go-sumtype[0] has completeness checking for sealed interfaces.
         | 
         | [0] https://github.com/BurntSushi/go-sumtype
        
         | thegeekpirate wrote:
         | > Able to have the confidence that we're checking for every
         | situation that could occur on an enum type across the codebase
         | is one less thing I need to worry about.
         | 
         | golangci-lint (https://github.com/golangci/golangci-lint) is an
         | absolute must, and includes
         | https://github.com/nishanths/exhaustive which will check this
         | for you.
        
           | henvic wrote:
           | my 50 cents:
           | 
           | I'd rather just use go vet + staticcheck. Way simpler, no
           | complex configuration, and no license concerns :)
           | 
           | and then you can create a file like the following and add
           | whatever analyzer provides value for your specific case
           | (including this exhaustive), easily. Example: https://github.
           | com/FiloSottile/mkcert/blob/master/analysis.g...
        
             | jatone wrote:
             | don't let perfect be the enemy of good. =)
        
           | preseinger wrote:
           | Configurable linters are usually best avoided, because it's
           | too easy to remove a check rather than fix your code, in a
           | crunch.
        
           | badhombres wrote:
           | thank you, I'm going to take a look at these!
        
       | [deleted]
        
       | whoisjohnkid wrote:
       | Regarding your point on iterators, I typically use a channel with
       | context; this lets you exit that range loop early and will
       | automatically stop the related go routine. (assuming it's context
       | aware)
       | 
       | e.g.
       | 
       | func myFunc(cx context.Context) {                 ctx, cancel =
       | context.WithCancel(cx)            defer cancel()            for
       | item := range iterator(ctx) {                  }     }
        
         | jerf wrote:
         | The major problem I have with that is performance is atrocious
         | [1] if the iterator isn't doing something very nontrivial.
         | Since the vast majority of iterators amount to incrementing at
         | most a handful of things and then returning something indexed
         | by those things, you're paying the cost of channel
         | communication per item but not getting that channel cost
         | amortized over any significant costs in the iteration itself.
         | If the thing using the iterator is _also_ not doing anything
         | significant (i.e., adding integers together), this has very bad
         | performance implications.
         | 
         | As a rule of thumb, the amount of work transferred by any
         | concurrency primitive should significantly exceed the cost of
         | the concurrency primitive itself. I do have a couple of uses of
         | this pattern where what is on the other side of the channel is
         | something reading off a network and parsing lines of JSON into
         | internal structs, in which case the overhead of the channel
         | isn't necessarily too bad. (In one case, it even chunks the
         | lines of JSON into a slice of several parsed structs, reducing
         | channel overhead even more.) But it's a terrible solution in
         | general; iterating over an array and doing any sort of very
         | fast "thing" to each element that only costs a handful of
         | assembler instructions, a very common use case, has terrible
         | overhead.
         | 
         | It's a real pity, because the _semantics_ of that solution are
         | pretty close to the right answer. But it 's a huge performance
         | trap. Something as basic as iteration needs to not be a huge
         | performance trap.
         | 
         | [1]: Relative to Go, anyhow. I haven't timed it directly but I
         | wouldn't be surprised that a channel-based iterator like that
         | would be comparable to Python's general iteration speed, or at
         | least not off by a very large factor. It's just that "Python's
         | normal level of performance" is "atrocious Go performance".
        
           | whoisjohnkid wrote:
           | Hmm, can you provide some examples of how a context based
           | iterator is slow?
           | 
           | If you need raw performance and you are just doing some
           | minimal operations on a slice then yeah you'd want to use a
           | simple for loop instead.
           | 
           | I typically use this pattern in situations where you have a
           | ton of data coming back where you want to avoid storing all
           | of that data in memory.
        
             | throwaway894345 wrote:
             | The problem with using channels is that these require
             | multiple goroutines and locking for a problem that's
             | inherently singlethreaded. Instead, you can define an
             | iterator as a function that returns a function:
             | func intSliceIter(ints []int) func() (int, bool) {
             | i := 0             return func() (int, bool) {
             | if i < len(ints) {                     ret := ints[i]
             | i++                     return ret, true                 }
             | return 0, false             }         }              iter
             | := intSliceIter([]int{0, 1, 2, 3, 4})              for x,
             | ok := iter(); ok; x, ok = iter() {
             | fmt.Println(x)         }
             | 
             | Of course, there's not much benefit to a SliceIter; this is
             | a contrived example, but you can apply this pattern in more
             | complicated cases as well. Similarly, you can define an
             | iterator as an interface (which is similar to bufio.Scanner
             | and a few others in the standard library--a closure is an
             | object is a closure):                   type IntIter
             | interface {             Next() (int, bool)         }
             | type IntSliceIter struct {             Cursor int
             | Ints []int         }              func (isi *IntSliceIter)
             | Next() (int, bool) {             if isi.Cursor <
             | len(isi.Ints) {                 ret := isi.Ints[isi.Cursor]
             | isi.Cursor++                 return ret, true             }
             | return 0, false         }
        
       | danudey wrote:
       | Reposurgeon was a pretty enticing option for us, as we're
       | currently in the migration of SVN to Git, and the string of tools
       | we have is kind of complicated to explain to newcomers.
       | 
       | Currently the workflow is svn-all-fast-export (SVN to Git) -> git
       | filter-repo (trim content from the repository) -> Git LFS (store
       | large files out-of-band), where each stage has to be manually
       | tested and written, and then hopefully put into a shell script or
       | Bash history or something.
       | 
       | Unfortunately, I have yet to get Reposurgeon working for us at
       | all. The documentation was inaccurate, in that it referred to
       | things which only existed in the Go version, and the Go version
       | went OOM (on a server with 384 GB of RAM) on every repository
       | I've tried it on. This includes just reading from an existing,
       | pruned svndump file, so it's not resource contention either.
       | 
       | Basically, the tool is great conceptually, but it absolutely does
       | not work for our use case. We had to stick with our existing,
       | messy, multi-component solution, which has worked surprisingly
       | well over the last two years, and since I'm the one doing most of
       | the processing, having kind of a messy system is relatively
       | acceptable for the time being.
        
       | mwcampbell wrote:
       | Personally, I would have gone with Kotlin for a translation like
       | this. It's a closer match to Python than Go, and I think his rule
       | swarm [1] approach to semi-automated translation would have been
       | effective. But then, he might not have liked the fact that, like
       | Python 3 and unlike Go, the Java platform treats strings
       | (particularly filenames) as a sequence of Unicode code points
       | rather than bytes.
       | 
       | [1]: http://esr.ibiblio.org/?p=8153
        
         | colin_mccabe wrote:
         | Go treats strings as a sequence of unicode code points.
        
           | fnoof wrote:
           | I believe they're treated as byte sequences:
           | https://go.dev/ref/spec#String_types
        
         | dbt00 wrote:
         | > It's a closer match to Python than Go
         | 
         | I like Kotlin, but I really think of Go as the most probable
         | outcome of someone saying "I'd like a staticly typed,
         | concurrent/parallel Python", and then mumbled "but I hate
         | exceptions". Go is very close to python in many respects.
        
       | nemo1618 wrote:
       | What I really missed was generic map-function-over-slice, which
       | could be        handled by adding a much narrower feature.
       | If one graded possible Go point extensions by a figure of merit
       | in which the        numerator is "how much Python expressiveness
       | this keeps" and the        denominator is "how simple and self-
       | contained the Go feature would be",        I think this one would
       | be top of list.                So: map as a functional builtin
       | takes two arguments, one x = []T and a        second f =
       | func(T)T. The expression map(x, f) yields a new slice in
       | which for each element of x, f(x) is appended.
       | 
       | I think this is an excellent way of evaluating new features, and
       | it represents a real missed opportunity for Go to explore new PL
       | territory. Instead, we're getting full-blown user-defined
       | generics, which increases the "denominator" far, far more than it
       | increases the "numerator."
        
         | preseinger wrote:
         | > a real missed opportunity for Go to explore new PL territory
         | 
         | But Go was not intended to explore new PL territory. Is that
         | not OK?
        
         | throwaway894345 wrote:
         | I also came from Python (15 years of experience) to Go and I
         | think newcomers to Go index too hard on terseness. The for-loop
         | equivalent for a map over a list is more characters, but it's
         | really straightforward and easily recognizable in the code. In
         | the general case, I'm glad that Go _doesn 't_ try to explore
         | new PL territory, optimizing instead for things that are
         | _known_ to improve developer productivity. None of this is to
         | say that Go has no room for improvement--only that  "terseness"
         | is not high on the list of improvements I'd like to see (for
         | example, I'd rather have sum types with exhaustive pattern
         | matching).
        
           | nemo1618 wrote:
           | For me, it's not a question of terseness -- it's more about
           | communicating intent, and not polluting the scope with
           | incidental variables. Everyone knows what map/filter/reduce
           | do. When reading new code, seeing "map" is better than seeing
           | a for loop: you don't have to think about the underlying
           | iteration at all, you can skip directly to the essence of the
           | transformation. As a side effect of this, when you _do_ see a
           | for loop, you can safely assume that something about the
           | iteration is non-trivial: maybe the loop exits early, for
           | example. I wrote a bit more about this here:
           | https://twitter.com/lukechampine/status/1463715093733122054
           | 
           | btw, by "new PL territory" I mean "reifying a small set of
           | container operations, without supporting user-defined
           | generics," which to my knowledge is not a position taken by
           | any mainstream language.
        
             | throwaway894345 wrote:
             | > For me, it's not a question of terseness -- it's more
             | about communicating intent, and not polluting the scope
             | with incidental variables.
             | 
             | A mapping for loop doesn't pollute scope with incidental
             | variables:                   results := make([]Result,
             | len(input))         for i := range input {
             | results[i] = callback(input[i])         }
             | 
             | ^ This only adds `results` to scope, which is the same as
             | `results := map(input, callback)`. In the for loop example,
             | the loop variable `i` is scoped to the loop.
             | 
             | Moreover, if you don't care about terseness, you can always
             | pull this out into a well-named function or annotate it
             | with a comment.
             | 
             | > Everyone knows what map/filter/reduce do
             | 
             | In isolation, but for complicated chains of
             | map/filter/reduce (especially with error handling logic in
             | languages which return errors rather than raising them as
             | exceptions) it's much easier for me to read the
             | corresponding for loop equivalent. Even my colleagues at a
             | Python shop had limits on the complexity of list
             | comprehensions beyond which point they were required to
             | rewrite into a for loop because while packing that
             | complexity into a single expression is elegant and clever,
             | it's not particularly readable or easy to understand.
             | 
             | I guess my view can be summarized as: for very simple
             | cases, map/filter/reduce are a bit clearer, but for those
             | same simple cases a for loop is still easily understood
             | _and_ a for loop 's readability scales better with
             | complexity.
        
         | pcwalton wrote:
         | ESR's proposal here is basically just another way of spelling
         | Python list comprehensions or Perl 5 map (which is likewise a
         | builtin, with special parsing rules). It's not "new PL
         | territory" by any means.
        
           | throwaway894345 wrote:
           | I think the "new PL territory" was referring to a type-safe
           | generic map builtin. Obviously Python and Perl don't have a
           | concept of type safety. That said, I still wouldn't call that
           | novel in the sense that a builtin version of a well-
           | understood generic function doesn't seem like a feat of
           | ingenuity.
        
             | lucian1900 wrote:
             | Most statically typed languages have a type safe generic
             | map or it can be easily built.
             | 
             | Just like with monomorphisation, Go just stubbornly rejects
             | all PL research younger than 40.
        
       | nauticacom wrote:
       | One thing that the people saying "just use a struct for keyword
       | arguments" are missing is that structs should signal intent, i.e.
       | "this is a concrete concept in the system." A Repository, Commit,
       | Message, Person, etc. struct are all _concepts_ in the domain,
       | whereas  "the arguments for this particular function" is not. I
       | think Go people are allergic to writing code that does anything
       | other than functionally work.
        
         | preseinger wrote:
         | > structs should signal intent
         | 
         | Isn't a collection of parameters to a function an intent?
        
           | nauticacom wrote:
           | Not really? I suppose what I meant by that was that, if you
           | make a struct, it should represent a concept that makes sense
           | outside of the context of passing it to one function in
           | particular; you're signalling that this collection of data
           | represents a concept in your program
        
             | throwaway894345 wrote:
             | > it should represent a concept that makes sense outside of
             | the context of passing it to one function in particular
             | 
             | Why? What's the utility? Fewer characters?
        
               | nauticacom wrote:
               | Because concepts in programming languages mean things. A
               | discrete, named entity (a struct) is a distinct concept
               | from a way to increase readability of a function call
               | (keyword arguments). Overloading them with the same
               | language construct is compressing disparate concepts.
               | It's the same thing when "Go has no set type" comes up
               | and people say "map[T]struct{}!" You might _implement_ a
               | set using a map, but they 're fundamentally different
               | concepts
        
               | preseinger wrote:
               | > A discrete, named entity (a struct) is a distinct
               | concept from a way to increase readability of a function
               | call (keyword arguments).
               | 
               | If you take                   func NewServer(addr string,
               | certKey, certCA []byte, ...) (*Server, error)
               | 
               | and coalesce the input parameters into
               | type ServerConfig struct {             Addr    string
               | CertKey []byte             CertCA  []byte             ...
               | 
               | then that type is still a meaningful domain concept in
               | your program. There is no minimum scope requirement for
               | discrete, named entities, is there?
        
             | preseinger wrote:
             | Why isn't "the collection of input parameters to this
             | function" a concept in my program? I understand it's
             | applicable scope is smaller than a e.g. DTO, but does that
             | matter categorically?
        
               | nauticacom wrote:
               | I suppose it could be, but imo it starts diluting the
               | value of domain modeling if everything is given the same
               | weight. A bundle of inputs to a function just doesn't
               | have the same weight as a struct representing a
               | repository or a commit or something, so modeling them as
               | the same concept just doesn't sit right with me. It could
               | be a personal thing, though.
        
               | preseinger wrote:
               | > A bundle of inputs to a function just doesn't have the
               | same weight as a struct representing a repository or a
               | commit or something
               | 
               | That's true. But IMO a language primitive can carry a
               | wide variety of semantics.
        
         | scndrycntct wrote:
         | > I think Go people are allergic to writing code that does
         | anything other than functionally work.
         | 
         | I think that's what Go was designed for, as a language. To be
         | readable, usable. It's uncaring for your personal programming
         | philosophies. I think that's why it's been successful.
        
           | nauticacom wrote:
           | I'd agree that it was one of their design goals, but I
           | wouldn't go as far as to say that it makes the language
           | "readable and usable." That depends on what your values are.
           | 
           | Go's philosophy (which clearly flows from its creators being
           | C-enthusiasts) is that the only thing that matters for
           | reading, writing, and understanding a program is what it
           | concretely does, i.e. what structures are created, where
           | values are stored, how computations are performed etc. If
           | that's also your philosophy, then of course it's going to
           | jive with you.
           | 
           | But plenty of people also have different philosophies. Maybe
           | you think the main thing that's important in crafting
           | programs is developing a rich domain vocabulary that
           | expresses concepts and how they interact. Maybe you think
           | that what's important is formal proof of both logical and
           | concrete correctness. In those cases, Go's rigorous
           | opposition to abstraction (coming from its philosophy that
           | what's important is concrete operations) will probably
           | irritate and slow you down.
           | 
           | I couldn't say exactly why it got popular. I'd guess that
           | some significant segment of programmers also share its
           | philosophy, but I have no evidence to back that up. Certainly
           | any reasonably uncontroversial language with a large suite of
           | libraries backed by Google is bound to have some level of
           | popularity.
        
             | opmac wrote:
             | > I couldn't say exactly why it got popular.
             | 
             | Probably because it has the backing of Google.
        
               | ts4z wrote:
               | I disagree. Go provides a low-runtime way of writing
               | programs, like C, without having to resort to managing
               | memory and threads super carefully. No VM, no
               | interpreter, fairly straightforward to imagine what the
               | compiler is doing.
               | 
               | You can do the same work in Java but you can't statically
               | link the JVM. You can sort of do these in Python, but the
               | compiler story is murky at best, and the language isn't
               | as type safe.
        
               | saghm wrote:
               | > No VM, no interpreter
               | 
               | That's not quite accurate; there is a runtime, it just
               | gets statically linked into the binary instead of needing
               | to be externally installed
        
               | throwaway894345 wrote:
               | No, Go literally doesn't have a VM or an interpreter. VMs
               | and interpreters are runtimes, but not all runtimes are
               | VMs or interpreters. Go executes native code.
        
               | earthboundkid wrote:
               | Counterpoint: Dart.
        
               | nauticacom wrote:
               | I thought of Dart as a counterpoint, but if anything it's
               | actually more proof. Dart kinda failed as a language in
               | the browser because Google didn't really _push_ it, and
               | when they did it got push _back_. Now that they 've
               | repurposed it for building mobile apps, it's surprisingly
               | popular. Sure, not Go-levels of popular, but leaps and
               | bounds more popular than if it were some scrappy OSS
               | project. And it's in a similar camp to Go: reasonably
               | uncontroversial (it's basically Java), large suite of
               | libraries, backed by Google.
        
               | throwaway894345 wrote:
               | Google has never meaningfully "pushed" Go. From Google's
               | perspective, Go is just a backend language that's a good
               | fit for some internal Google applications. I don't think
               | they care tremendously that other people use it, although
               | they certainly don't mind. On the other hand, Google
               | strategically wanted a robust frontend ecosystem (hence
               | investing heavily in Dart and V8) because getting more
               | applications off of PCs and onto the web meant more user
               | data up to collect and more opportunity to serve ads.
               | 
               | In particular, I don't understand how Go is more Java-
               | like than Dart.                   Feature            |
               | Java | Dart | Go
               | -------------------+------+------+----         jit
               | compilation    | yes  | yes  | no         inheritance
               | | yes  | yes  | no         classes            | yes  |
               | yes  | no         nominal subtyping  | yes  | yes  | no
               | native binaries    | no   | no   | yes         static
               | artifact[0] | no   | no   | yes         static typing
               | | yes  | opt  | no         value types        | no   | no
               | | yes
               | 
               | What other features do Java and Go have in common that
               | they don't also share with Dart?
               | 
               | [0]: For sanity's sake, we'll assume this means "are
               | static artifacts common/default" and not "is it
               | technically possible to produce a static artifact"
               | because for some sufficiently broad definition of static
               | artifact the answer can be yes for any language (e.g.,
               | Docker images).
        
               | [deleted]
        
           | SquishyPanda23 wrote:
           | > To be readable, usable.
           | 
           | Go doesn't particularly value readability or usability. For
           | example, the short variable name convention makes it harder
           | to read code you're unfamiliar with, and there are a number
           | of noticeable usability shortcomings, some of which are
           | mentioned in the article.
           | 
           | I think Go's design goals were really to (1) reduce
           | compilation time, which explains why Go has human programmers
           | do work that compilers do in other languages, (2) be
           | statically typed and compiled, so you can use it conveniently
           | for microservices, and (3) have syntax somewhat similar to
           | Python.
           | 
           | I think of Go as the successor to Java. Go is to Python as
           | Java was to C++. That, plus the integration with many
           | libraries is why it's taken off in some niches.
        
             | throwaway894345 wrote:
             | Honestly the biggest selling point of Go to me was that
             | it's simple and consistent. It enforces strong opinions
             | that I may disagree with, but it means everyone does things
             | generally the same way which is an important component in
             | readability. With respect to short variable names, the
             | convention is to only use them for very local scopes (e.g.,
             | using `i` as a loop index variable). In particular, I don't
             | need to learn a new language or run a daemon just to
             | compile code with a few dependencies and ship a static
             | binary. Similarly, I don't need to configure CI pipelines
             | just to publish packages or documentation. I don't need an
             | IDE, I don't need to shop around for a test framework or an
             | external web server process because they're built in. I
             | don't have to think about what version of the runtime
             | and/or dependencies are installed on my target system. Plus
             | performance is good and the ecosystem is substantial.
             | Personally from experience, I weight these kinds of
             | concerns a lot higher than whatever bells and whistles are
             | available inside of the language.
        
           | LandR wrote:
           | I don't find go readable in the slightest, the syntactic
           | bureaucracy is just too high.
           | 
           | There is a forest full of code hiding a trees worth of
           | business logic, always.
           | 
           | Go is one of the least expressive languages I've ever used.
        
             | sangnoir wrote:
             | > Go is one of the least expressive languages I've ever
             | used
             | 
             | That is by design, and when it comes to "programming in the
             | large" - a winning formula. I keep repeating this response:
             | I worked on a Perl codebase with a medium-sized team. Perl
             | is _very_ expressive, and my teammates did not hold back. I
             | can tell you that is a _nightmare_ to debug or add a new
             | edgecase to a  "clever" Perl 1-liner, usually it involved
             | making the code "less expressive". So, I'll take Go over
             | the more expressive languages in a team setting _any_ day.
        
               | nauticacom wrote:
               | It's a spectrum, though. A language can be between 0
               | expressiveness (Go) and 100 expressiveness (Perl, maybe
               | Lisps), and claiming that the only way to avoid the
               | mysterious evil team member who will wreck your codebase
               | is to patronizingly limit them "for their own sake" is
               | insulting
        
               | LandR wrote:
               | Often times the developers moaning about complex code
               | basically learned if statements and for loops and then
               | were done learning.
               | 
               | But we still have to write code that these people
               | understand. It makes no damn sense.
               | 
               | Sometimes, sure, people write horribly complex code, but
               | sometimes it's just developers who have stopped learning.
               | They see something that isn't immediately familiar and
               | discard it as too complex and make no attempt at trying
               | to learn.
               | 
               | What I don't get is why we have to pander to these
               | people.
        
               | sangnoir wrote:
               | Not for their sake - for mine. I avoid people and places
               | that make my life unnecessarily difficult, _especially_
               | if I have to do support and can be called at 3am to
               | resolve urgent issues.
               | 
               | My needs are pretty basic: I like code that is easy to
               | understand and easy to change more than writing code that
               | leaves a smug smile on my face. I read more code than I
               | write, so YMMV.
               | 
               | The fewer surprises, the better for me, and so far, the
               | collaborative codebases I've encountered the least number
               | of surprises have consistently been in Go (the other
               | languages I've been paid to work with are Javascript,
               | Perl, Python, Java, and Scala).
        
             | preseinger wrote:
             | > the syntactic bureaucracy is too high.
             | 
             | Huh? What do you consider syntactic bureaucracy? Go has
             | like 25 keywords and no sigils -- the least "syntactically
             | bureaucratic" language I'm aware of!
             | 
             | > Go is one of the least expressive languages I've ever
             | used.
             | 
             | This is definitely true. Of course expressiveness is not
             | strictly a virtue!
        
               | nauticacom wrote:
               | > What do you consider syntactic bureaucracy?
               | 
               | I would assume "amount of syntax required to express a
               | given concept," with the use of the word "bureaucracy"
               | implying that some concepts require too much syntax
               | relative to their complexity (something that depends on
               | your values). The classic example being mapping over a
               | slice.
        
               | preseinger wrote:
               | Ah, so in this framing, syntax is another way of saying
               | amount of code?
               | 
               | Here's some Rust code                   pub fn
               | read(&self) -> u64 {
               | self.counts.values().fold(0, |acc, x| acc + x)         }
               | 
               | Here's some analogous Go code                   func (w
               | *Whatever) Read() uint64 {             var total uint64
               | for _, v := range w.values {                 total += v
               | }             return total          }
               | 
               | The former is certainly fewer characters than the latter.
               | But to me it represents _more_ syntactic bureaucracy, not
               | less. There are more sigils, more language concepts I
               | need to understand, more _types of syntax_ to express the
               | same thing. It's 20% of the SLoC, but parsing it requires
               | more implicit knowledge, and takes no less time, versus
               | parsing the latter.
               | 
               | YMMV, of course. None of this is objective.
        
               | nauticacom wrote:
               | Ah, that's interesting! I'm not the person who used
               | "syntactic bureaucracy" but I did interpret it roughly
               | like that, yes. I'm not sure what phrase I would use to
               | describe "more syntactical constructs" as you're saying,
               | but it's interesting how we saw the term differently.
               | 
               | Similarly, even though I'm not a big fan of Rust, I would
               | personally prefer to encounter the Rust snippet. The way
               | I read code, I'm already building up mental models of
               | things in my head, so adding more (e.g. what fold is)
               | isn't that big of a deal to me. I think I'm a person who
               | is able to look at a function call and not have the
               | desire to dig into its source, though, which I don't
               | think is the way everybody (and most certainly not the
               | designers of Go) feels. Plus once I understand the
               | concept, even if it's a lot less universally applicable
               | than fold, I can reuse my understanding of it throughout
               | the system and possibly throughout multiple systems.
               | 
               | Like you said, I think this is largely a subjective
               | thing. I just find it objectionable when people, on
               | either side of the fence, come in and say "abstracting
               | over concepts and possibly making them first class is
               | _always_ better " or "... _always_ worse. "
        
           | danudey wrote:
           | Specifically, Go was designed to be effectively and safely
           | writeable and readable by mediocre programmers.
        
             | geoka9 wrote:
             | Some of the best software developers I've seen are mediocre
             | programmers :)
        
             | jhawk28 wrote:
             | Mediocre programmers can still write bad Go code. Their
             | code is neither safe or readable.
        
               | jrockway wrote:
               | Don't take the obvious flamebait. HN's favorite comment
               | on any Go article is "it's designed for bad programmers",
               | implying that if you like it, you're bad at programming.
               | I'm good at programming and like Go, so that implication
               | is clearly false.
        
               | throwaway894345 wrote:
               | I'm a Go enthusiast, but I think the charitable
               | interpretation is that you don't have to expend lots of
               | mental energy to read and write Go code. Even if you have
               | a lot of mental capacity, you can put the excess toward
               | interesting problems rather than reasoning about object
               | lifetimes or hidden control flow or complex interactions
               | between obscure features. If anyone uses "Go is designed
               | for bad programmers" as an insult to Go programmers,
               | they're only arguing against themselves (and lacking the
               | cognitive faculties to notice).
        
         | jasonhansel wrote:
         | IMHO, if the arguments to a function are just a bunch of
         | heterogeneous values that cannot be sensibly grouped together,
         | then that's usually a sign that the function needs to be
         | refactored (e.g. because it's trying to do a bunch of unrelated
         | things, or because it couples tasks together in an unnatural
         | way).
        
         | throwaway894345 wrote:
         | Not everyone is a Kingdom of Nouns purist. Even most OOP
         | proponents that I've spoken with reject Kingdom of Nouns
         | because it's pretty indefensible (in the worst case it leads to
         | banana-gorilla-jungle problems[0] and in the best case it
         | imposes arbitrary and unnatural restrictions on program
         | design). Note also that "modern Java" and "modern C#" and
         | "modern C++" and "modern Python" virtually all reject these
         | kinds of designs and end up looking a lot more like Go with
         | respect to their use of structs/classes.
         | 
         | [0]: https://www.johndcook.com/blog/2011/07/19/you-wanted-
         | banana/
        
           | nauticacom wrote:
           | Even if you're not doing OOP you're modeling your domain in
           | one way or another. Whether those models are explicit and
           | expressive or not, and how you define those qualities, is
           | another question. In neither case are the arguments to a
           | particular function typically a concept in your domain
        
             | throwaway894345 wrote:
             | > Even if you're not doing OOP you're modeling your domain
             | in one way or another.
             | 
             | Using structs to bundle function parameters doesn't
             | preclude or inhibit using them for domain modeling.
        
               | nauticacom wrote:
               | Sure, but there's a _purpose-built tool_ that doesn 't
               | overload an already existing concept, and it's called
               | keyword arguments
        
         | CGamesPlay wrote:
         | Well, "Command to Create a Repository" could easily be a
         | concept which contains all of the options necessary to create a
         | Repository, which is a different concept.
         | 
         | Now, there is a problem that all of the struct fields have to
         | have sane zero values for this to work, but I think that is a
         | problem which would naturally arise with keyword arguments as
         | well.
        
           | nauticacom wrote:
           | Sure, if you're pushing things through a command bus or
           | something. Otherwise you're just stretching it. If you
           | wouldn't use it in a sentence to describe a use-case, I'd say
           | that it's probably not a concept.
           | 
           | Would it? Only if you allow default values, which is a
           | separate discussion. And even then only if your only
           | mechanism for default values doesn't let the definitions
           | specify what, exactly, the default value is.
        
             | preseinger wrote:
             | > Only if you allow default values, which is a separate
             | discussion.
             | 
             | Keyword args practically require default values, I think?
             | In any case, while this feature (like every feature)
             | definitely delivers value, it's the considered position of
             | the Go authors that this feature, over time, has a net
             | negative impact on program maintainability.
        
       | azth wrote:
       | It's not surprising that the code base grew when reimplemented in
       | golang. It would have probably been even shorter had it been
       | rewritten in Python itself.
       | 
       | Just the other day, I was able to condense over 15 lines of
       | golang code into 3 lines (could also have been 2 lines) in a
       | Python-like syntax, both reducing code length, and substantially
       | increasing readability as it would make the underlying logic
       | clearly stand out instead of having several loop and map
       | constructs.
        
         | throwaway894345 wrote:
         | I think you have to be very careful before claiming readability
         | gains from fewer lines of code. I've done a lot of Python
         | programming and for simple things like mapping a single
         | function over a list, a list comprehension or generator
         | expression can indeed be a tiny bit more readable than a for
         | loop, but combining that with some filtering or flattening and
         | colleagues (and my future self) get frustrated. I've seen the
         | same thing with overly fancy iterator chaining in Rust.
         | 
         | There's some temptation to think that terse==readable, but in
         | practice this rarely extends beyond the simplest cases.
        
           | azth wrote:
           | I didn't claim that terse is always more readable. It was in
           | the example I gave, and practically every golang program
           | could be written in a more terse, more readable, and less
           | error prone way in Python/Java/C#/etc.
        
             | throwaway894345 wrote:
             | I understand that you didn't claim this explicitly, but
             | it's a common misconception so I addressed it anyway. While
             | I have no doubt that a given Go program could be written
             | more tersely in Python, Java, or C# I've written and
             | operated enough programs in those languages to know that
             | the results are rarely if ever more readable or less error-
             | prone. I think Go's preferences for simplicity over
             | cleverness and explicitness over terseness are significant
             | factors in these readability and quality disparities.
             | Newline characters aren't where your bugs are coming from.
        
           | formerly_proven wrote:
           | I didn't yet get around to writing Go in any significant
           | capacity (it's not in an interesting spot for me) but I've
           | had to read some and to me it seems like about half of all Go
           | code does approximately nothing. It seems really weird to me
           | that Go doesn't have a Result/Optional type (or sum types in
           | general) and instead prefers multiple return values instead,
           | and consequentially does not have error-handling operators
           | but requires if err != nil repeated approximately 9000 times
           | per file.
        
             | throwaway894345 wrote:
             | This seems like a real sticking point for a lot of people.
             | Personally, I don't seem to think about code volume in
             | units of "lines" but rather in units of "complexity" and
             | "locality". I would like to see Result and Optional types,
             | but in practice everyone understands that (nil, err) is the
             | same as Result::Err and that (*T, nil) is the same as
             | Result::Ok. Of course, you miss out on monadic properties,
             | but after extensively using the language, that's a
             | _feature_ 99% of the time. I would say give it a shot; I
             | was skeptical at first, but this is surprisingly not an
             | issue IMO.
             | 
             | The real issues (IMO) with Go error handling are the clunky
             | `errors.Is()` and `errors.As()` functions for catching
             | specific error values and types respectively as well as
             | annotating errors to get the requisite context (this may
             | already have a solution which just hasn't yet become
             | idiomatic across the ecosystem). In the meanwhile, I just
             | wrap errors with extra context which isn't super satisfying
             | but largely does the trick, e.g., `return
             | fmt.Errorf("writing to database: %w", err)`. Rust seems to
             | have similar problems with respect to different patterns
             | and libraries for error handling, despite having a standard
             | Result type.
        
             | Polygator wrote:
             | One of the issues I had with Go was that even though
             | functions can return pseudo-tuples (multiple return
             | values), there are no tuples anywhere else in the language.
             | So you can't chain functions which return (T, err), easily
             | create a slice of their return values, feed their output to
             | a channel, etc. Just made handling operations on slices so
             | tedious
        
           | nemo1618 wrote:
           | > There's some temptation to think that terse==readable, but
           | in practice this rarely extends beyond the simplest cases
           | 
           | A quick glance at any array language (APL, J, K, Q, etc.)
           | confirms this. ;)
        
       | ynth7 wrote:
       | What's the point in reposting the same old Go complaints? It's
       | esr this time?
       | 
       | Respect for his early contributions, but he's basically what PG
       | would be if he had not become obscenely rich; rehashing old
       | points, relying on history to have a reputation, contributing
       | little novel output.
       | 
       | He sure can complain about things he has no intention to put
       | effort into improving though.
        
       | LysPJ wrote:
       | Really interesting read. Especially the suggestion for how to
       | support iterators.
       | 
       | It should be noted that the writeup is a year old, which
       | (understandably) means that the comments on generics are a little
       | out of date.
        
       ___________________________________________________________________
       (page generated 2021-12-10 23:01 UTC)