On Wed, May 4, 2022 at 12:13 AM Axel Wagner <axel.wagner...@googlemail.com>
wrote:

> On Wed, May 4, 2022 at 8:42 AM Will Faught <w...@willfaught.com> wrote:
>
>> Yes. I understand what you suggested and I understood how it *would*
>>> work, if implemented that way. But why is that the best way to compare
>>> them? Doing it that way has a bunch of semantic implications, some of which
>>> are perhaps counterintuitive, which I tried to mention.
>>>
>> I explained that in detail in the subsequent paragraphs.
>>
>
> I don't believe you did. Looking over it again, those paragraphs explain
> *what* you are proposing. Not *why* those semantics are the right ones.
>
>
I wrote in those subsequent paragraphs:

The reason to include capacity in comparisons, aside from it being
> convenient when doing comparisons, is that the capacity is an observable
> attribute of slices in regular code. Programmers are encouraged to reason
> about slice capacity, so it should be included in comparisons. `cap(S1[:1])
> != cap(S1[:1:1])` is true, therefore `S1[:1] != S1[:1:1]` should be true,
> even though `len(S1[:1]) == len(S1[:1:1])` is true.


Do you agree that is a good thing, yes or no, and if not, why?

I wrote in the proposal:

This approach to comparisons for functions, maps, and slices makes all
> values of those types immutable, and therefore usable as map keys.


Do you agree that is a good thing, yes or no, and if not, why?

I wrote in the proposal an example of how slices work in actual Go code,
then asked:

Do you expect `c` to be true? If not (it's false, by the way), then why
> would you expect `make([]int, 2) == make([]int, 2)` to be true?


What was your answer? Yes or no? This isn't rhetorical at this point, I'm
actually asking, so please answer unambiguously yes or no.

If your answer was yes, then you don't understand Go at a basic level. If
your answer was no, then my argument is that it would be consistent for
comparisons of *built-in* slices to work the same way.

Do you agree that is a good thing, yes or no, and if not, why?

One very specific case is that your semantics consider a[0:0:0] ==
> b[0:0:0], if a and b come from different slices *even though the
> difference between them is unobservable without unsafe*. You make the
> argument that capacity should matter (which is one of the questions I
> posed), because of observability. So, by the same token, that difference
> shouldn't matter here, should it?
>
>
I don't follow why `a[0:0:0] == b[0:0:0]` would be true if they have
different array pointers. I'm arguing that they shouldn't be equal *because*
the array pointers are different. What are you saying is observable, but
not being accounted for by this proposal? Note that `a[0] = 0; b[0] = 0;
a[0] = 1; b[0] == 1` can observe whether the array pointers are the same.


> But really, the point isn't "which semantics are right". The point is
> "there are many different questions which we could argue about in detail,
> therefore there doesn't appear to be a single right set of semantics".
>
>
I've already addressed this point directly, in a response to you. You
commented on the particular example I'd given (iterating strings), but not
on the general point. I'd be interested in your thoughts on that now. Here
it is again:

Just because there are two ways to do something, and people tend to lean
> different ways, doesn't mean we shouldn't pick a default way, and make the
> other way still possible. For example, the range operation can produce per
> iteration an element index and an element value for slices, but a byte
> index and a rune value for strings. Personally, I found the byte index
> counterintuitive, as I expected the value to count runes like slice
> elements, but upon reflection, it makes sense, because you can easily count
> iterations yourself to have both byte indexes and rune counts, but you
> can't so trivially do the opposite. Should we omit ranging over strings
> entirely just because someone, somewhere, somehow might have a minority
> intuition, or if something is generally counterintuitive, but still the
> best approach?



>>
>>> Note that the language doesn't mention "runtime.slice", BTW. So, even if
>>> we did it that way, we would have to phrase it as "two slices are equal, if
>>> they point to the same underlying array and have the same length and
>>> capacity", or somesuch. This would still not define whether make([]T, 0) ==
>>> make([]T, 0), though.
>>>
>>>
>> Perhaps I'm at fault for not being precise in my wording, but the intent
>> was to specify slice comparisons as encompassing the array pointer, the
>> length, and the capacity. It doesn't matter if other fields are added
>> later, or if the type is renamed.
>>
>
> The language spec does not know about array pointers. I'm not saying "a
> field could be added later". I'm saying "one of the fields you are talking
> about does not exist, as it is a choice made by gc, not something
> prescribed by the spec".
>
>
>From https://go.dev/ref/spec#Slice_types:

A slice is a descriptor for a contiguous segment of an underlying array and
> provides access to a numbered sequence of elements from that array.


It doesn't matter to me whether we refer to the slice's array as a
"descriptor" of an array, or it "points" to an array. It refers to a
specific array in memory, period. It's trivial enough to specify that slice
comparisons have a way to distinguish between arrays in memory, whether
that's through pointers or "just knowing" in the same way that they "just
know" how to access and use the array in the first place. I refer to it as
a pointer because I know that's how it works under the hood, and people
know how pointers work.

Does this fully address your point, yes or no, and if not, why?

reflect.DeepEqual does not compare the pointers for capacity 0 slices
>>> <https://go.dev/play/p/vO2m-1zzCc8>, BTW. To drive home how ambiguous
>>> this question actually is, given that you assumed it woudl.
>>>
>>>
>>
>>
The proposal doesn't depend on reflection or unsafe behavior. It was just a
lazy way of mine to inspect what Go does in a corner case. I think making
it clear what `make` does when the length is 0 is the solution to this, if
it already isn't clear.


> It only depends on what the array pointer is. That behavior actually
>> matches my original specification: "Slice values are equal if they have the
>> same array pointer, length, and capacity."
>>
>
> No, it does not. The example I linked to uses different array pointers,
> but compares as equal by reflect.Equal.
>
>

Again, it doesn't matter what `reflect.DeepEqual` does. I referred to it as
an example of deep comparisons, that's all. If `reflect.DeepEquals` doesn't
consider array pointers in slices (which makes sense, since it does a
deep/logical comparison, not a shallow/literal one), then so be it, that's
not what's being proposed here.


> So, two of the `make([]T, 0)` expressions should *not* be equal if each
>> one gets a unique array pointer, which is consistent with channel
>> comparisons too.
>>
>
> Channels compare as equal "if they were created by the same call to make
> or if both have value nil". We can't do the same for slices, as slices can
> be manipulated after calling make (by re-slicing).
>
> Note, FTR, that the spec does *not* talk about channels being pointers.
> Which goes to my point above - we can't define slice comparisons in terms
> of what gc does, we have to define it based on what the language spec says.
>
>
>From https://go.dev/ref/spec#Comparison_operators:

Two channel values are equal if they were created by the same call to make


Two of the `make([]T, 0)` expressions not being equal would be consistent
with channels in that the slices they evaluate to were created by different
calls to `make`.

It would be a bad idea to *blindly* compare two slices that came from
>> disparate sources, because the odds of them being shallowly equal are
>> infinitesimal. If I read a `[]int` off my hard drive, and another off the
>> Internet, I shouldn't compare them blindly, because the odds are extremely
>> good that their array pointers, lengths, or capacities aren't equal, so the
>> comparison won't likely reflect the equality of the elements.
>>
>
> To me, this says that *in most cases* the semantics you are suggesting is
> really unhelpful. Imagine we would say "floats are comparable, but only if
> they where both created by mathematic operations from the same value - if
> you read one float from the internet and another from disk, don't compare
> them, they might have the same value, but compare unequal". That would be
> ridiculous, wouldn't it?
>
>

It depends on what the floats *mean*. An example with integers is `token.Pos
<https://pkg.go.dev/go/token#Pos>`: instead of having every token position
stored in an AST contain the file name, file offset, line, and column, the
offset range for every `token.File` is tracked in a `token.FileSet`, which
tracks line endings separately. A Pos is a plain integer that points into
the global offset range in the FileSet, which can trivially be mapped into
the corresponding File, and a corresponding Position that contains the
associated offset, line, and column. The upshot is we can store unambiguous
file offsets in the AST using only a small integer, and use it to later
retrieve more rich info when needed. For every file offset, there is an
unambiguous Pos that points to it.

Does it make sense to compare a Pos from one FileSet with a Pos from
another FileSet? No. `pos1 == pos2` might well type-check as valid, but
it's meaningless. It's like comparing different units of measurement.

And so it is with slices coming from disparate sources.

Floats don't contain pointers. Slices do. I addressed this in points
elsewhere. Please address those there.


> The same is true for pointers: if I get a pointer type from hard drive
>> data, and another from Internet data, what exactly do you think the odds
>> are that those are actually going to be the same value in memory? Shallow
>> comparisons are most useful when you stick a value somewhere, and then go
>> fishing for it later, or encounter it later. In those cases, you don't care
>> about equivalence, you just care about identity, because you *know* it's
>> there. Deep comparisons are best for data from disparate sources.
>>
>
> *Exactly*. There is no single notion of comparison, which is always (or
> even "most of the time") what you'd want. Therefore, it's best not to have
> any notion of comparison, lest people shoot themselves in the foot.
>
>

The point is that there *are* two ways to compare them: shallow (pointer
values themselves) and deep (comparing the dereferenced values). If we made
`==` do deep comparisons for pointers, we'd have no way to do shallow
comparisons. Shallow comparisons still allow for deep comparisons, but not
the other way around.


> We *could* do deep comparisons for pointers
>>>>
>>>
>>> Not without allowing for a bunch of unpleasant consequences, at least.
>>> For example, this code would hang forever:
>>>
>>> type T *T
>>> var t1, t2 T
>>> t1, t2 = &t2, &t1
>>> fmt.Println(t1 == t2)
>>>
>>> Note that it is *very common* to have circular pointer structures. For
>>> example, in a doubly-linked list.
>>>
>>
>> Right. I didn't mean it would be useful, or good. The same argument
>> applies to slice comparisons.
>>
>
> It seems to me, that you are exactly confirming our point here, which is
> that for pointers, there is a single, unambiguously good way to define
> comparisons.
>
>
No, that doesn't confirm that for pointers, there is a single, unambiguous
way to compare them. Your example shows why shallow comparisons for
pointers is good. But there are still ways (perhaps more than one) to
compare them deeply (e.g. as DeepEquals does). The same is true for slices.


> Note that *originally*, you said that there wasn't - that if we say
> "slices can not be compared, because there is no unambiguously good way to
> do so", we should also disallow pointers, because there is no unambiguously
> good way to do so.
>
> Well, there isn't. The alternative you suggested is not good.
>
>
Originally I said there wasn't what? An unambiguously good way to define
comparisons for pointers? I never said that. I've said that shallow
comparisons, where possible (arrays don't have a shallow version, for
example), are best. I probably argued at some point that slice comparisons
should be shallow because pointer comparisons are shallow, so if you think
slice comparisons should be deep, then you should also think that pointer
comparisons should be deep, or something like that. Please provide a
quotation for context.


> For slices, there *are* many differently good ways to define that
> comparison - and the specific bounds you set, seem to allow for unintuitive
> results, such as a[0:0:0] != b[0:0:0], even if their difference can't be
> observed.
>
>

I don't understand what these a and b slices are supposed to show. If the
arrays for a and b are the same, you can observe that. If they're
different, you can observe that. Everything is observable about those three
fields in slices.


>>
>>>
>>>
>>>> , but we don't, because shallow comparisons are useful, and we can
>>>> dereference pointers when we need to do deep comparisons. As I argued
>>>> above, this is exactly the same situation for slices regarding shallow and
>>>> deep comparisons.
>>>>
>>>>
>>>>> Why should we compare pointer values when doing so won't compare the
>>>>>> referenced, logical values? Because sometimes we want to compare just the
>>>>>> pointer values because it's fast, and pointers are small, and you can
>>>>>> conclude logical equivalence if they're equal. Sometimes shallow/literal
>>>>>> comparisons are useful, and sometimes deep/logical comparisons are 
>>>>>> useful.
>>>>>>
>>>>>> Just as pointer comparisons are shallow, so too are
>>>>>> comparisons for types that contain pointers. I included the Slice1000
>>>>>> example above specifically to address your point. Based on your argument
>>>>>> here, I assume your answer to the question about `c` in that example 
>>>>>> would
>>>>>> be "yes," however the answer is no, according to current Go behavior.
>>>>>> Comparison of structs containing pointers does a shallow comparison of 
>>>>>> the
>>>>>> pointer value, not the value it references. My argument is that under the
>>>>>> hood, Go slices work the same way.
>>>>>>
>>>>>> I'm proposing a shallow comparison, not a deep comparison, and that's
>>>>>> arguably a feature here. I highlighted the time complexity for a reason,
>>>>>> because I recall someone in the Go Team at one point arguing somewhere 
>>>>>> that
>>>>>> doing a logical comparison would be too slow, since one of the benefits 
>>>>>> of
>>>>>> Go comparisons is that their time complexity is small and 
>>>>>> well-understood.
>>>>>> Checking for equality for your struct-based type won't ever cause your
>>>>>> program to slow or hang; it's "safe."
>>>>>>
>>>>>> The point isn't to provide equivalence operations; it's to provide
>>>>>> useful comparison operations that are consistent with the other types'
>>>>>> comparison operations, to make all types consistent and simplify
>>>>>> <https://go.dev/doc/faq#map_keys> the language. We could provide a
>>>>>> separate equivalence operation, perhaps something like `===` that behaves
>>>>>> like `reflect.DeepEquals`, but that's a separate issue. Shallow slice
>>>>>> comparisons *do* allow you to conclude that elements are equal if
>>>>>> slices compare equal, and we can still iterate slices manually to compare
>>>>>> elements.
>>>>>>
>>>>>> On Mon, May 2, 2022 at 9:58 PM Rob Pike <r...@golang.org> wrote:
>>>>>>
>>>>>>> * Functions: Compare the corresponding memory addresses. The time
>>>>>>> complexity is constant.
>>>>>>>
>>>>>>> There are cases involving closures, generated trampolines, late
>>>>>>> binding and other details that mean that doing this will either
>>>>>>> eliminate many optimization possibilities or restrict the compiler
>>>>>>> too
>>>>>>> much or cause surprising results. We disabled function comparison for
>>>>>>> just these reasons. It used to work this way, but made closures
>>>>>>> surprising, so we backed out and allow comparison only to nil.
>>>>>>>
>>>>>>> * Maps: Compare the corresponding `*runtime.hmap` (pointer) values.
>>>>>>> The time complexity is constant.
>>>>>>> * Slices: Compare the corresponding `runtime.slice` (non-pointer
>>>>>>> struct) values. The time complexity is constant.
>>>>>>>
>>>>>>> In LISP terms, these implementations do something more like `eq`, not
>>>>>>> `equal`. I want to know if the slices or maps are _equivalent_, not
>>>>>>> if
>>>>>>> they point to identical memory. No one wants this semantics for slice
>>>>>>> equality. Checking if they are equivalent raises difficult issues
>>>>>>> around recursion, slices that point to themselves, and other problems
>>>>>>> that prevent a clean, efficient solution.
>>>>>>>
>>>>>>> Believe me, if equality for these types was efficient _and_ useful,
>>>>>>> it
>>>>>>> would already be done.
>>>>>>>
>>>>>>> -rob
>>>>>>>
>>>>>>> On Tue, May 3, 2022 at 2:41 PM Will Faught <will....@gmail.com>
>>>>>>> wrote:
>>>>>>> >
>>>>>>> > You seem to have misunderstood the point. It's an idea for
>>>>>>> changing the language. You're just demonstrating the current behavior,
>>>>>>> which is what would be changed. The argument is to make `make([]int, 2) 
>>>>>>> ==
>>>>>>> make([]int, 2)` legal, and evaluate to false.
>>>>>>> >
>>>>>>> > On Mon, May 2, 2022 at 8:22 PM Kurtis Rader <kra...@skepticism.us>
>>>>>>> wrote:
>>>>>>> >>
>>>>>>> >> On Mon, May 2, 2022 at 7:44 PM will....@gmail.com <
>>>>>>> will....@gmail.com> wrote:
>>>>>>> >>>
>>>>>>> >>> ```
>>>>>>> >>> type Slice1000[T any] struct {
>>>>>>> >>>     xs *[1000]T
>>>>>>> >>>     len, cap int
>>>>>>> >>> }
>>>>>>> >>>
>>>>>>> >>> func (s Slice1000[T]) Get(i int) T {
>>>>>>> >>>     // ...
>>>>>>> >>>     return s.xs[i]
>>>>>>> >>> }
>>>>>>> >>>
>>>>>>> >>> func (s Slice1000[T]) Set(i int, x T) {
>>>>>>> >>>     // ...
>>>>>>> >>>     s.xs[i] = x
>>>>>>> >>> }
>>>>>>> >>>
>>>>>>> >>> var xs1, xs2 [1000]int
>>>>>>> >>>
>>>>>>> >>> var a = Slice1000[int]{&xs1, 1000, 1000}
>>>>>>> >>> var b = Slice1000[int]{&xs2, 1000, 1000}
>>>>>>> >>> var c = a == b
>>>>>>> >>> ```
>>>>>>> >>>
>>>>>>> >>> Do you expect `c` to be true? If not (it's false, by the way),
>>>>>>> then why would you expect `make([]int, 2) == make([]int, 2)` to be true?
>>>>>>> >>
>>>>>>> >>
>>>>>>> >> No. Did you actually try your hypothetical `make([]int, 2) ==
>>>>>>> make([]int, 2)`? When I do so using the source below this reply the Go
>>>>>>> compiler emits the error "slice can only be compared to nil". Which is 
>>>>>>> what
>>>>>>> I expect given the specification for the Go language. This seems like an
>>>>>>> example of the XY Problem. What caused you to open this thread?
>>>>>>> >>
>>>>>>> >> package main
>>>>>>> >>
>>>>>>> >> import (
>>>>>>> >> "fmt"
>>>>>>> >> )
>>>>>>> >>
>>>>>>> >> func main() {
>>>>>>> >> fmt.Printf("%v\n", make([]int, 2) == make([]int, 2))
>>>>>>> >> }
>>>>>>> >>
>>>>>>> >> --
>>>>>>> >> Kurtis Rader
>>>>>>> >> Caretaker of the exceptional canines Junior and Hank
>>>>>>> >
>>>>>>> > --
>>>>>>> > You received this message because you are subscribed to the Google
>>>>>>> Groups "golang-nuts" group.
>>>>>>> > To unsubscribe from this group and stop receiving emails from it,
>>>>>>> send an email to golang-nuts...@googlegroups.com.
>>>>>>> > To view this discussion on the web visit
>>>>>>> https://groups.google.com/d/msgid/golang-nuts/CAKbcuKheNk99JrYJ8u6knu15LSwf6nZXxD6_UqUOF_1JhFVHjA%40mail.gmail.com
>>>>>>> .
>>>>>>>
>>>>>> --
>>>>>> You received this message because you are subscribed to the Google
>>>>>> Groups "golang-nuts" group.
>>>>>> To unsubscribe from this group and stop receiving emails from it,
>>>>>> send an email to golang-nuts...@googlegroups.com.
>>>>>>
>>>>> To view this discussion on the web visit
>>>>>> https://groups.google.com/d/msgid/golang-nuts/CAKbcuKicTHJZZ7yAYKO96MhCPQct%3DQZs07S4%3D-_vXvmoe_ndqA%40mail.gmail.com
>>>>>> <https://groups.google.com/d/msgid/golang-nuts/CAKbcuKicTHJZZ7yAYKO96MhCPQct%3DQZs07S4%3D-_vXvmoe_ndqA%40mail.gmail.com?utm_medium=email&utm_source=footer>
>>>>>> .
>>>>>>
>>>>> --
>>>> You received this message because you are subscribed to the Google
>>>> Groups "golang-nuts" group.
>>>> To unsubscribe from this group and stop receiving emails from it, send
>>>> an email to golang-nuts+unsubscr...@googlegroups.com.
>>>> To view this discussion on the web visit
>>>> https://groups.google.com/d/msgid/golang-nuts/a533b35c-6ab7-4fe3-9698-6a0f0a71f091n%40googlegroups.com
>>>> <https://groups.google.com/d/msgid/golang-nuts/a533b35c-6ab7-4fe3-9698-6a0f0a71f091n%40googlegroups.com?utm_medium=email&utm_source=footer>
>>>> .
>>>>
>>> --
>>> You received this message because you are subscribed to a topic in the
>>> Google Groups "golang-nuts" group.
>>> To unsubscribe from this topic, visit
>>> https://groups.google.com/d/topic/golang-nuts/b-WtVh3H_oY/unsubscribe.
>>> To unsubscribe from this group and all its topics, send an email to
>>> golang-nuts+unsubscr...@googlegroups.com.
>>> To view this discussion on the web visit
>>> https://groups.google.com/d/msgid/golang-nuts/CAEkBMfH%3Dy8re0yXYXr%3DUEYnBVsRxouM98ozyOH6uO9jbtueFSA%40mail.gmail.com
>>> <https://groups.google.com/d/msgid/golang-nuts/CAEkBMfH%3Dy8re0yXYXr%3DUEYnBVsRxouM98ozyOH6uO9jbtueFSA%40mail.gmail.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAKbcuKjpPBJQwyBCsyKXRSK_L%2BTKxmVXR1ymV0efmi7MjE-BbA%40mail.gmail.com.

Reply via email to