Live data from Hacker News

Notes on the Go translation of Reposurgeon (2020)

gitlab.com

1–10 of 155 posts

Re: Notes on the Go translation of Reposurgeon (2020)

#2
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.

Re: Notes on the Go translation of Reposurgeon (2020)

#4
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) {
      
  }
}

Re: Notes on the Go translation of Reposurgeon (2020)

#5

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) { } }

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".

Re: Notes on the Go translation of Reposurgeon (2020)

#6
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.

Re: Notes on the Go translation of Reposurgeon (2020)

#7

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 properti…

> 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.

Re: Notes on the Go translation of Reposurgeon (2020)

#9
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.

Re: Notes on the Go translation of Reposurgeon (2020)

#10
post #5

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) { } }

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…

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.

Post reply on HN