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