On Tue, May 3, 2022 at 7:27 PM Ian Lance Taylor <i...@golang.org> wrote:

> On Tue, May 3, 2022 at 6:08 PM Will Faught <will.fau...@gmail.com> wrote:
> >
> > My apologies, it seems that "reply all" in the Google Groups UI doesn't
> send email to individuals like "reply all" in Gmail does, just the group.
> Response copied below.
> >
> >> Yes, it can. That's not the issue. The issue is that whether those
> >> pointer values are equal for two func values is not intuitive at the
> >> language level. When using shared libraries (-buildmode=shared) you
> >> can get two different pointer values for references to the same
> >> function. When using method values you can get the same pointer value
> >> for references to method values of different expressions of the same
> >> type. When using closures you will sometimes get the same value,
> >> sometimes different values, depending on the implementation and the
> >> escape analysis done by the compiler.
> >
> >
> > Interesting. This certainly differs from how I pictured functions
> working (more like C function pointers with extra steps). I'd be curious to
> know more about the details. Do you know if that's documented somewhere?
>
> I'm not aware of any specific documentation on the topic, sorry.
>
> The fact that C function pointers are guaranteed to compare equal can
> actually be a performance hit at program startup for dynamically
> linked C programs.  I wrote up the details of the worst case several
> years ago at https://www.airs.com/blog/archives/307.  There are other
> lesser issues.
>
>
Thanks! :)


>
> > Just curious, what would be the cost if things were rejiggered under the
> hood to make function comparisons work? Would any language features be
> impossible, or would it be worse compiler/runtime complexity/performance,
> or both?
>
> Regardless of compiler/runtime issues, this would introduce language
> complexity, similar to the issues with slices.  We would have to
> precisely specify when two func values are equal and when they are
> not.  There is no intuitive answer to that.
>
> Does a program like this print true or false?
>
> func F() func() int { return func() int { return 0 } }
> func G() { fmt.Println(F() == F()) }
>
>
It would print false, because the function literal creates a new allocation
(according to the rule I sketched out). I can see the desire to optimize
that, but personally when I write code like that, I'm thinking, "and then
return a new function." Causing an allocation isn't surprising behavior
here, and so neither is uniqueness in terms of comparisons.


> What about a program like this:
>
> func H(i int) func() *int { return func() *int { return &i } }
> func J() { fmt.Println(H(0) == H(1)) }
>
>
It would print false for the same reason.


> Whatever we define for cases like this some people will be ready to
> argue for a different choice.  The costs of forcing a decision exceed
> the benefits.
>

So on the balance, the cost of making a decision is worth it for something
big like dependencies or generics, but not function equality. Well, I guess
that's fair enough, but it seems like one could use that kind of argument
to undermine any language change, though, including dependencies and
generics. It doesn't seem like the function equality rule I sketched out
would add much, if any, language complexity. It's only one sentence:
"Function values are equal if they were created by the same function
literal or declaration."

> Regarding expectations, many new Java programmers came from JavaScript
> (like myself), so the confusion is understandable, but it's not something
> that necessarily needs to be considered. Arguably, old Java programmers
> would find `==` confusing for structs, since it doesn't compare references.
> Bad assumptions are best prevented by proper education and training, not by
> omitting language features. Wrong expectations aren't the same as foot-guns.
>
> I don't agree.  Unexpected behavior is a footgun.


I wrote that wrong expectations aren't foot-guns, not that unexpected
behaviors aren't foot-guns. Wrong expectations, as in "I can call this
pointer-receiver method on this unaddressable value," or "since zero values
are useful, I can set keys and values in this zero-value map." Downloading
a compiler for some new language I haven't bothered to learn, typing in
Java-like stuff, and then being upset when it doesn't work isn't a problem
of unexpected behavior, it's a problem of wrong expectations (usually
misunderstandings or ignorance).


> Go is intended to
> be a simple language.  When special explanation is required, something
> has gone wrong.
>
>
The Go language spec is *full* of special explanations. The section on
comparisons <https://go.dev/ref/spec#Comparison_operators> is quite
detailed and complicated. I recently had to ask here why the range
operation doesn't work for type set unions of slices and maps, which you
very kindly answered, if I remember correctly. How is slice equality
different in terms of special explanation?

I've argued that slice comparisons make Go even simpler and more consistent
with various examples, analogies, and so on. Please see my response to
Axel, if you haven't already. Do you have a specific counter argument to
any specific argument that I've made regarding simplicity or consistency?


> In saying this I don't at all claim that Go is perfect.  There are
> places where we made mistakes.  But I don't think that our decision to
> not define == on slices or functions is one of them.
>
>
I'd like to be able to reach the same conclusions you have, in the same way
you did, so that's what I'm trying to understand. It's difficult to
understand your position when I probe your understanding with an argument,
and I receive counter arguments like "this would introduce language
complexity," "the costs of forcing a decision," "Go is intended to be a
simple language," and "I don't at all claim that Go is perfect" that don't
specifically address my points. Those arguments could be used to quash
anything new, like dependencies or generics, so they don't seem to hold
water by themselves, in my opinion.


>
> > >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?
>
> The fact that range over []byte and string are different may well have
> been a mistake.  It can certainly be convenient, but it trips people
> up.  (Certainly others may disagree with me on this.)
>
>
> > >The best path is to pick the best way for the common case, and make the
> other way possible
>
> I do not agree.  The best path is to make no choice, and force the
> program writer to be explicit about what they want.
>
> Ian
>

-- 
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/CAKbcuKhrUgFi2htgb8Dp627fjU_j8LsgQdc1HQtoAfdc8uC9oA%40mail.gmail.com.

Reply via email to