Circling back to this, because it came up today again.

Here's the generic function I want to write. It comes up in a lot of
function composition, which in turn comes up in a lot of interface adapters
and such:

func maybeAssign[T any](dst *T, src T, name string) {
    if *dst != nil { // any can't be compared with nil
        panic(fmt.Errorf("too many %s arguments", name))
    }
    *dst = src
}

Using this function in each assignment, instead of inlining the four-line
construct with panic, can save a lot of space and make code a lot more
readable.
The above doesn't work, because not every type can be assigned nil.
The following also doesn't work:

func maybeAssign[T comparable](dst *T, src T, name string) {
    var zero T
    if *dst != zero { // interface and other nillable types can't be
compared to zero
        panic(fmt.Errorf("too many %s arguments", name))
    }
    *dst = src
}

Because interface values aren't comparable. (As aren't chans, maps, etc,
but THOSE can be jammed into various interface constructs, whereas "any
interface" cannot, because "interface{}" doesn't actually mean "any
interface")

Let me try to answer:

> Why is the *specific* split into (interfaces, pointers, slices,
functions, maps, channels) and (numbers, booleans, strings, structs,
arrays) a particularly important one?

Because, while go tries very hard to make sure every storable type has a
"zero value," it somehow decides that you can't necessarily COMPARE to that
zero value.
But the whole point of zero values is that you can tell them from non-zero
values!
So, the language has introduced a work-around with the concept of "I can
compare to nil" for these reference types that aren't comparable to their
zero value.
But generics don't allow us to sense or make use of this, so generics can't
express what the regular language can express. Even a very simple case like
the above, can't currently be expressed, and this leads to more verbose
code that's harder to read and harder to work with. (Granted, this is my
opinion, but I'm not alone.)

If the language instead changes such that chans and interfaces and maps
become comparable, then that's fine -- that solves the problem! But that
seems like a much bigger change than a constraint that senses exactly the
"can compare to nil" semantic.

If the language instead changes so that nil means "the zero value" in
general, and it so happens that these nil-comparable types can be compared
to nil without any particular qualification, that also solves the problem.
That might indeed be a good solution -- but if so, it'd be nice to know
what we can do to make that happen, and what the other opposition to that
change might be, because that change also feels much less narrow than a
"nil" type constraint.


Sincerely,

Jon Watte


--
"I find that the harder I work, the more luck I seem to have." -- Thomas
Jefferson


On Tue, Oct 3, 2023 at 10:41 PM Axel Wagner <axel.wagner...@googlemail.com>
wrote:

> Oh (sorry, being forgetful) and re "it's less of a new mechanism than
> introducing a zero identifier": #62487
> <https://github.com/golang/go/issues/62487> introduces *even less* new
> mechanism, by expanding comparison to (and assignment of) `nil` to all
> types inside a generic function. It's not a new class of constraint, it
> just special-cases `nil` a bit more. So it is still a far more general
> mechanism, that solves more problems than `nilable` constraint, while
> requiring fewer (or at worst the same number of) new concepts.
>
> On Wed, Oct 4, 2023 at 7:36 AM Axel Wagner <axel.wagner...@googlemail.com>
> wrote:
>
>> (correction: It should be Convert[J isinterface, T J]. I changed the name
>> from I to J to be more readable and then missed one occurrence)
>>
>> On Wed, Oct 4, 2023 at 7:33 AM Axel Wagner <axel.wagner...@googlemail.com>
>> wrote:
>>
>>> On Wed, Oct 4, 2023 at 6:54 AM Jon Watte <jwa...@gmail.com> wrote:
>>>
>>>> > where it is important to permit only type arguments that can be
>>>> compared to nil
>>>>
>>>> I see! As in, if we somehow got a "equalszero" constraint, then that
>>>> constraint would solve the problem I illustrate.
>>>> I believe that assertion is correct, but I also believe that is a
>>>> stronger assertion, and also that it introduces more of a new concept than
>>>> a simple "nil" constraint. (Unless you're looking for some way to make
>>>> "any" work, and introduce a zero keyword or something...)
>>>>
>>>
>>> Yes, that is what #61372 <https://go.dev/issue/61372> proposes:
>>> Introduce a `zero` predeclared identifier (!) that is assignable to any
>>> type and comparable to any type. With some discussion about whether it
>>> should only apply inside generic code or not. There is no proposal (as far
>>> as I know) for anything like an "equalszero" constraint, as every type can
>>> be assigned a meaningful comparison to its zero value, so it seems we
>>> should just allow it for all types.
>>>
>>> To be clear, the criticism of a `nilable` constraint is
>>> 1. It only solves a subset of the problem we are seeing. You gave
>>> examples from that subset. I gave some examples of problems we are seeing
>>> that are *not* in that subset.
>>> 2. It is not really clear this particular subset is particularly
>>> important. Why is the *specific* split into (interfaces, pointers,
>>> slices, functions, maps, channels) and (numbers, booleans, strings,
>>> structs, arrays) a particularly important one?
>>> 3. As long as that is not clear, it seems more prudent to focus on
>>> mechanisms that solve more of the problems we are seeing.
>>>
>>> FWIW I could, personally, get more (though still not fully) on board
>>> with an `isinterface` constraint, that would allow *only* interfaces.
>>> It would still allow assignment and comparison to `nil`. But it seems far
>>> clearer to me, that interfaces can be singled out. While a `nil` interface
>>> is categorically an invalid value, the same is not true for `nil`
>>> pointers/maps/channels/funcs *in general*. Any of those kinds of types
>>> could still have methods callable on them that work perfectly fine (by
>>> doing an `if receiver == nil` check in the method). You categorically can't
>>> call a method on a `nil` interface.
>>>
>>> And an `isinterface` constraint could still conceivable be useful for
>>> many of the examples you mentioned. Or it would allow
>>>
>>> func Convert[J isinterface, T I](s []T) []J {
>>>     out := make([]I, len(T))
>>>     for i, v := range s {
>>>         out[i] = J(v)
>>>     }
>>>     return out
>>> }
>>>
>>> I'd still not be convinced this is really worth it, but at least it
>>> seems clearer why that particular subset of types deserves to be singled
>>> out. In fact, many people have argued that the interface zero value really
>>> shouldn't have been spelled `nil`, because interfaces have so little in
>>> common, conceptually, to other "nilable" types.
>>>
>>>
>>>>
>>>> Also, there's the ergonomics of having to make a zero value instance.
>>>> Maybe we can rely on the compiler to optimize it away, but at a minimum it
>>>> adds another required line of code in the implementation. E g:
>>>>
>>>> func MaybeNuke[T nil](b bool, val T) T {
>>>>   if b {
>>>>     return nil
>>>>   }
>>>>   return val
>>>> }
>>>>
>>>> func MaybeNuke(T zero](b bool, val T) T {
>>>>   if b {
>>>>     var nope T // an extra line!
>>>>     return nope
>>>>   }
>>>>   return val
>>>> }
>>>>
>>>> func MaybeNuke(T any](b bool, val T) T {
>>>>   if b {
>>>>     return zero[T]{} // maybe? seems weird
>>>>   }
>>>>   return val
>>>> }
>>>>
>>>> This is because not all zero values can be instantiated inline with
>>>> simply T{}.
>>>>
>>>> Sincerely,
>>>>
>>>> Jon Watte
>>>>
>>>>
>>>> --
>>>> "I find that the harder I work, the more luck I seem to have." --
>>>> Thomas Jefferson
>>>>
>>>

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

Reply via email to