Live data from Hacker News

a[low:high:max] in Golang – A Rare Slice Trick

build-your-own.org

41–50 of 131 posts

Re: a[low:high:max] in Golang – A Rare Slice Trick

#41
post #3

Earlier quoted context omitted.

I don't know much Go but what does it mean to "accidentally" append to an immutable array? Are there just no particular guards for arrays and they are saying something which should be treated as immutable?

Go has no way to ensure "const correctness". If you pass around a slice (which you'll be doing a lot, since it's essentially Go's equivalent of a vector), everyone can modify the slice however they please. It's just a fat pointer. So yes, something which should be treated as immutable. The way Go works here easily leads to bugs, especially when concurrency is involved and slices get captured or used by goroutines. If…

"one goroutine has access to the value" :O

If you stick to "values" Go works as originally advertised (see below). With values iirc the consensus became that for high performance the channel overhead was too much of a hit and so back to locks and 'traditional' concurrency.

The concurrency issue in PLTs is not a logical puzzle. It's a performance challenge revolving around copying stuff and hand-offs. With multicores thrown in, it seems to really require addressing the challenges once and for all (as services) at the OS and possibly even hardware layer. IF we have efficiencies at the hw & os around 'message passing', any variation on CSP would address the concurrency issue, "by design", as it says below in Go's "Effective Go" documentation.

https://web.archive.org/web/20091111073232/http://golang.org...

https://web.archive.org/web/20091113154825/http://golang.org...

Share by communicating

Concurrent programming is a large topic and there is space only for some Go-specific highlights here.

Concurrent programming in many environments is made difficult by the subtleties required to implement correct access to shared variables. Go encourages a different approach in which shared values are passed around on channels and, in fact, never actively shared by separate threads of execution. Only one goroutine has access to the value at any given time. Data races cannot occur, by design. To encourage this way of thinking we have reduced it to a slogan:

Do not communicate by sharing memory; instead, share memory by communicating."

Re: a[low:high:max] in Golang – A Rare Slice Trick

#42
post #37

Earlier quoted context omitted.

Slices (largely) behave like this in most languages Is that really the case? I can’t think of many languages that let you construct a view into the middle of array, pass that view as an argument to a function call, and allow that function to add new values into your array via the view. In Python or JS, for example, the “slice” would just be a brand new array, right?

If the slice syntax creates a copy, I would argue that it's simply syntactic sugar for array copying, not actually producing a slice (i.e. there is no slice type ). But yes, `somefunc(ary[1:])` in Python produces a copy, not a reference to the underlying value. You could build a more "true" slice class, but the builtin stuff doesn't do that. JavaScript is similar. Java however has `List .sublist` which largely behave…

Another example is typed arrays in JavaScript, which have both .slice() and .subarray(). One creates a copy, and one creates a new view into the same underlying memory.

Re: a[low:high:max] in Golang – A Rare Slice Trick

#43
post #21

Earlier quoted context omitted.

Yep. Depending on how the slice was constructed , not how it is used. Slices (largely) behave like this in most languages, Go's contribution is mostly that slices and append are ubiquitous, so pretty much every Go coder is exposed to it. Prior to generics, literally any alternative was so much more work they essentially haven't been used. That may change in the future now that we do have (very simplistic) generics, b…

> Slices (largely) behave like this in most languages Only on assignment, not in appending. > Go's contribution is mostly that slices and append are ubiquitous Go's contribution is the conflation of slices and vectors, which in most languages are separate (or really most languages only have the latter and don't provide access to backing arrays, thus precluding this specific confusion).

In C++, until C++20’s std::span, you would just use std::vector for stuff you can modify and const std::vector for stuff you can’t.

I think the real problem is that Go’s type system doesn’t catch the common error of keeping a reference to something you don’t own, or similar errors. C# catches some of these errors by letting you return a IReadOnlyList or some other restricted type. Golang has ways to narrow certain types, but the slice type is primitive and has no narrowed read-only variation. C# instead forces you to pay a higher runtime cost, because you’re paying a lot more for indirection with IReadOnlyList.

People fret about the difference between T[] and List in C#, and they do come with different performance characteristics… Go’s choice to use slices everywhere does have a certain advantage that you’re getting the fast path everywhere, and you’re spending less time thinking about which one to use.

Not trying to say that Go is “right” here, it’s just my viewpoint that these language design decisions are rational.

Re: a[low:high:max] in Golang – A Rare Slice Trick

#44

0. Only 2 of 3 values are necessary. 1. Can 1 value be unspecified? 2. Can 2 values be unspecified? 3. Can 3 values be unspecified? 4. Are conflicting values interpreted as intersection of ranges rather than union? Note 1-3. With 2 values, I believe x[:] is how to lift a sized array into a more generic slice type.

Regarding conflicting ranges—

In a[i:j:k], 0 <= i, i <= j, j <= k, and k <= cap(a). If not, then the operation will panic.

Re: a[low:high:max] in Golang – A Rare Slice Trick

#45
Question: what is the reason for the silent copy when append exceeds the original slice cap?

It's a footgun avoided by reading the spec and (maybe) remembering it in practice, but it feels like it would be safer to throw a comp error and force the user to deal with it when a user is trying to exceed the cap of the underlying array?

Alternative is defensively using len() and cap() for slice ops in which case error-ing out feels more ergonomic.

Re: a[low:high:max] in Golang – A Rare Slice Trick

#47
post #22
post #13

Earlier quoted context omitted.

"X for me but not for thee" is very much the vibe Go gives me, yeah. I mean, I have completely replaced my adhoc Python use with it and I'm much happier. But it's terrifying to use to build large systems.

Part of it is that "large systems" are almost all combinations of small systems with proto boundaries, so it's not much of an actual risk unless you're making giant monolith code.

"Proto" meaning protobuffers?

Re: a[low:high:max] in Golang – A Rare Slice Trick

#48
post #37

Earlier quoted context omitted.

Slices (largely) behave like this in most languages Is that really the case? I can’t think of many languages that let you construct a view into the middle of array, pass that view as an argument to a function call, and allow that function to add new values into your array via the view. In Python or JS, for example, the “slice” would just be a brand new array, right?

If the slice syntax creates a copy, I would argue that it's simply syntactic sugar for array copying, not actually producing a slice (i.e. there is no slice type ). But yes, `somefunc(ary[1:])` in Python produces a copy, not a reference to the underlying value. You could build a more "true" slice class, but the builtin stuff doesn't do that. JavaScript is similar. Java however has `List .sublist` which largely behave…

It doesn’t look like any of those allow you to arbitrarily add and remove elements from the parent list in the same way Go does (if I understand Go slices correctly).

Java does let you make modifications, but only under very tight restrictions:

The semantics of the list returned by this method become undefined if the backing list (i.e., this list) is structurally modified in any way other than via the returned list. (Structural modifications are those that change the size of this list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.)

I take that to mean the parent list can’t be modified at the same time, nor can multiple views be modified.

So, I think Go’s ability to do those structural modifications on slices is rather unusual (as well as error-prone and not obviously useful).

Re: a[low:high:max] in Golang – A Rare Slice Trick

#49
post #37

Earlier quoted context omitted.

If the slice syntax creates a copy, I would argue that it's simply syntactic sugar for array copying, not actually producing a slice (i.e. there is no slice type ). But yes, `somefunc(ary[1:])` in Python produces a copy, not a reference to the underlying value. You could build a more "true" slice class, but the builtin stuff doesn't do that. JavaScript is similar. Java however has `List .sublist` which largely behave…

Another example is typed arrays in JavaScript, which have both .slice() and .subarray(). One creates a copy, and one creates a new view into the same underlying memory.

You can’t change the length of a JS ArrayBuffer, or write past the end of your view.

Re: a[low:high:max] in Golang – A Rare Slice Trick

#50

Question: what is the reason for the silent copy when append exceeds the original slice cap? It's a footgun avoided by reading the spec and (maybe) remembering it in practice, but it feels like it would be safer to throw a comp error and force the user to deal with it when a user is trying to exceed the cap of the underlying array? Alternative is defensively using len() and cap() for slice ops in which case error-ing…

Erroring on appending to a slice would require checking every call to append for an error. I'd find it more surprising for append to error on resize since append implies a growable array.
Post reply on HN