Re: [go-nuts] Preemptive interfaces in Go

2022-08-09 Thread Tim Peoples
I'm Sorry -- when I said "readability reviewer" I was referring to a very 
google-specific term about how they ensure the author of a change 
understands the language they're writing. Granted, it's been a while since 
I worked there but, at that time, each change request (aka MR, PR, etc...) 
required approval from (1) a code owner and (2) someone with *readability* for 
the language in question -- and, if the author covered both of those, an 
"LGTM" from someone else was still required (i.e. *Minimum Two Brains*). 
Regardless, no code change could be submitted without someone with 
*readability* being involved (either the author or a reviewer).

Each language had their own procedures about how *readability* would be 
granted. For Go, each and every CR from a non-readability SWE would get 
assigned to a random *readability reviewer*  to ensure the code meets 
certain standards and is *idiomatically Go.*  It took me ~15 months and 
just under 60 CRs to get Go *readability* (compared to 4 months and 3 CRs 
for Python).

On Tuesday, August 9, 2022 at 1:08:42 PM UTC-7 bse...@computer.org wrote:

> On Tue, Aug 9, 2022 at 1:52 PM Tim Peoples  wrote:
>
>> Yeah, I'm with Burak on this one. The interface usage you're describing 
>> Henry is exactly the kind of thing I'm talking about.  While on the surface 
>> it may seem advantageous -- in fact, I also tried writing Go that way when 
>> I first started -- my *readability* reviewers at Google did well to 
>> enlighten me about the many problems this can cause with Go -- some of 
>> which rog was kind enough to enumerate.
>
>
> I think "readability" is not the right metric to use here. "Code 
> comprehension" (comprehensibility?) should be the right metric. Readability 
> does not always imply it can be easily comprehended. Java is readable, but 
> not necessarily comprehensible. I argue that Go code is more comprehensible 
> than code written in most other languages, because you can understand all 
> the implications of the code using mostly "local knowledge", that is, 
> knowledge you can gain by reading pieces of code "close" to the point of 
> interest. Wherever you have interfaces, you need non-local knowledge to 
> understand what's going on.
>
>  
>
>>
>> Also, since originally posting this yesterday, I've come to learn that my 
>> new shop is not only utilizing preemptive interface definitions but also a 
>> complete dependency injection framework and rather strict adherence to 
>> *Clean 
>> Architecture*™ 
>> 
>>  
>> -- (likely from the scripts in this repo 
>> ) which goes a long way 
>> towards explaining why so much of the code looks like Java written in Go 
>> syntax.
>>
>> On Tuesday, August 9, 2022 at 8:33:07 AM UTC-7 bse...@computer.org wrote:
>>
>>> On Mon, Aug 8, 2022 at 11:27 PM Henry  wrote:
>>>
 I am sure that many of us have been on that journey. After using Go for 
 some time, we discover some practices that are not necessarily in 
 agreement 
 with the existing "adages" but effectively solve our problems.  

 For me, if the data type is mutable, I prefer returning interfaces. It 
 would be something like this:
 ```
 type Student interface {
//...
 }

 type studentImpl struct {
//...
 }

 func NewStudent(id string) Student {
return {
   //...
}
 }
 ```
 There is a bit of history why I use this approach. For a struct with a 
 mutex, I wanted to ensure that the user did not accidentally copy the 
 struct. Nowadays we have *go vet* to give us a warning, but this was 
 before *go vet* had this functionality. So, I return a pointer to the 
 struct and hide it behind an interface. That way, it hides the 
 implementation details from the user and the user can pass the object 
 around without knowing whether it has a mutex or not.

 And then I ended up with some *constructors* returning structs and 
 some returning interfaces. To ensure consistency, my colleagues and I 
 decided to return interfaces for all mutable objects. For immutable 
 objects, we return structs.

 The nice thing about this approach is that it makes the syntax a lot 
 cleaner as you have to deal with fewer pointers. 
 ```
 //instead of this
 func Update(student *Student) {
   //...
 }
 func UpdateMany(students []*Student){
   //...
 }

 //now you have this
 func Update(student Student) {
   //...
 }
 func UpdateMany(students []Student){
   //...
 }
 ```
 Some members in the team came from higher level languages and they 
 found working with pointers a bit awkward, so we made some accommodation 
 for them. 

>>>
 There are times when I need to *upgrade* some of these mutable 
 objects, and this approach 

Re: [go-nuts] Preemptive interfaces in Go

2022-08-09 Thread burak serdar
On Tue, Aug 9, 2022 at 1:52 PM Tim Peoples  wrote:

> Yeah, I'm with Burak on this one. The interface usage you're describing
> Henry is exactly the kind of thing I'm talking about.  While on the surface
> it may seem advantageous -- in fact, I also tried writing Go that way when
> I first started -- my *readability* reviewers at Google did well to
> enlighten me about the many problems this can cause with Go -- some of
> which rog was kind enough to enumerate.


I think "readability" is not the right metric to use here. "Code
comprehension" (comprehensibility?) should be the right metric. Readability
does not always imply it can be easily comprehended. Java is readable, but
not necessarily comprehensible. I argue that Go code is more comprehensible
than code written in most other languages, because you can understand all
the implications of the code using mostly "local knowledge", that is,
knowledge you can gain by reading pieces of code "close" to the point of
interest. Wherever you have interfaces, you need non-local knowledge to
understand what's going on.



>
> Also, since originally posting this yesterday, I've come to learn that my
> new shop is not only utilizing preemptive interface definitions but also a
> complete dependency injection framework and rather strict adherence to *Clean
> Architecture*™
> 
> -- (likely from the scripts in this repo
> ) which goes a long way
> towards explaining why so much of the code looks like Java written in Go
> syntax.
>
> On Tuesday, August 9, 2022 at 8:33:07 AM UTC-7 bse...@computer.org wrote:
>
>> On Mon, Aug 8, 2022 at 11:27 PM Henry  wrote:
>>
>>> I am sure that many of us have been on that journey. After using Go for
>>> some time, we discover some practices that are not necessarily in agreement
>>> with the existing "adages" but effectively solve our problems.
>>>
>>> For me, if the data type is mutable, I prefer returning interfaces. It
>>> would be something like this:
>>> ```
>>> type Student interface {
>>>//...
>>> }
>>>
>>> type studentImpl struct {
>>>//...
>>> }
>>>
>>> func NewStudent(id string) Student {
>>>return {
>>>   //...
>>>}
>>> }
>>> ```
>>> There is a bit of history why I use this approach. For a struct with a
>>> mutex, I wanted to ensure that the user did not accidentally copy the
>>> struct. Nowadays we have *go vet* to give us a warning, but this was
>>> before *go vet* had this functionality. So, I return a pointer to the
>>> struct and hide it behind an interface. That way, it hides the
>>> implementation details from the user and the user can pass the object
>>> around without knowing whether it has a mutex or not.
>>>
>>> And then I ended up with some *constructors* returning structs and some
>>> returning interfaces. To ensure consistency, my colleagues and I decided to
>>> return interfaces for all mutable objects. For immutable objects, we return
>>> structs.
>>>
>>> The nice thing about this approach is that it makes the syntax a lot
>>> cleaner as you have to deal with fewer pointers.
>>> ```
>>> //instead of this
>>> func Update(student *Student) {
>>>   //...
>>> }
>>> func UpdateMany(students []*Student){
>>>   //...
>>> }
>>>
>>> //now you have this
>>> func Update(student Student) {
>>>   //...
>>> }
>>> func UpdateMany(students []Student){
>>>   //...
>>> }
>>> ```
>>> Some members in the team came from higher level languages and they found
>>> working with pointers a bit awkward, so we made some accommodation for
>>> them.
>>>
>>
>>> There are times when I need to *upgrade* some of these mutable objects,
>>> and this approach has proven to be quite flexible. It also plays nicely
>>> with code generators.
>>>
>>> Some people may disagree with this approach, but I have been using it
>>> ever since: return interface for mutable objects, return structs for
>>> immutable objects.
>>>
>>
>> I am one of those who disagrees. I have not seen any benefit from having
>> interfaces for data objects other than making other developers happy. In my
>> opinion, this amounts to emulating another language in Go. There are cases
>> where this might make sense, but as a general principle, I think it should
>> be avoided. Data is data. There are no implementation details to hide.
>>
>>
>>
>>> On Tuesday, August 9, 2022 at 3:09:10 AM UTC+7 t...@timpeoples.com
>>> wrote:
>>>
 I can't speak to the *auto-generated swagger client* case but I
 believe gRPC is still doing things the right way -- in that the framework
 defines an interface I (the framework API consumer) then implements.  IOW:
 I don't see that as a "java style interface" (where the interface defines
 the API contract).

 I suspect you and I are saything the same thing.

 t.

 On Monday, August 8, 2022 at 12:51:29 PM UTC-7 bse...@computer.org
 wrote:

> On Mon, Aug 8, 2022 at 12:51 

Re: [go-nuts] Preemptive interfaces in Go

2022-08-09 Thread Tim Peoples
Yeah, I'm with Burak on this one. The interface usage you're describing 
Henry is exactly the kind of thing I'm talking about.  While on the surface 
it may seem advantageous -- in fact, I also tried writing Go that way when 
I first started -- my *readability* reviewers at Google did well to 
enlighten me about the many problems this can cause with Go -- some of 
which rog was kind enough to enumerate.

Also, since originally posting this yesterday, I've come to learn that my 
new shop is not only utilizing preemptive interface definitions but also a 
complete dependency injection framework and rather strict adherence to *Clean 
Architecture*™ 
 
-- (likely from the scripts in this repo 
) which goes a long way 
towards explaining why so much of the code looks like Java written in Go 
syntax.

On Tuesday, August 9, 2022 at 8:33:07 AM UTC-7 bse...@computer.org wrote:

> On Mon, Aug 8, 2022 at 11:27 PM Henry  wrote:
>
>> I am sure that many of us have been on that journey. After using Go for 
>> some time, we discover some practices that are not necessarily in agreement 
>> with the existing "adages" but effectively solve our problems.  
>>
>> For me, if the data type is mutable, I prefer returning interfaces. It 
>> would be something like this:
>> ```
>> type Student interface {
>>//...
>> }
>>
>> type studentImpl struct {
>>//...
>> }
>>
>> func NewStudent(id string) Student {
>>return {
>>   //...
>>}
>> }
>> ```
>> There is a bit of history why I use this approach. For a struct with a 
>> mutex, I wanted to ensure that the user did not accidentally copy the 
>> struct. Nowadays we have *go vet* to give us a warning, but this was 
>> before *go vet* had this functionality. So, I return a pointer to the 
>> struct and hide it behind an interface. That way, it hides the 
>> implementation details from the user and the user can pass the object 
>> around without knowing whether it has a mutex or not.
>>
>> And then I ended up with some *constructors* returning structs and some 
>> returning interfaces. To ensure consistency, my colleagues and I decided to 
>> return interfaces for all mutable objects. For immutable objects, we return 
>> structs.
>>
>> The nice thing about this approach is that it makes the syntax a lot 
>> cleaner as you have to deal with fewer pointers. 
>> ```
>> //instead of this
>> func Update(student *Student) {
>>   //...
>> }
>> func UpdateMany(students []*Student){
>>   //...
>> }
>>
>> //now you have this
>> func Update(student Student) {
>>   //...
>> }
>> func UpdateMany(students []Student){
>>   //...
>> }
>> ```
>> Some members in the team came from higher level languages and they found 
>> working with pointers a bit awkward, so we made some accommodation for 
>> them. 
>>
>
>> There are times when I need to *upgrade* some of these mutable objects, 
>> and this approach has proven to be quite flexible. It also plays nicely 
>> with code generators.
>>
>> Some people may disagree with this approach, but I have been using it 
>> ever since: return interface for mutable objects, return structs for 
>> immutable objects.
>>
>
> I am one of those who disagrees. I have not seen any benefit from having 
> interfaces for data objects other than making other developers happy. In my 
> opinion, this amounts to emulating another language in Go. There are cases 
> where this might make sense, but as a general principle, I think it should 
> be avoided. Data is data. There are no implementation details to hide.
>
>  
>
>> On Tuesday, August 9, 2022 at 3:09:10 AM UTC+7 t...@timpeoples.com wrote:
>>
>>> I can't speak to the *auto-generated swagger client* case but I believe 
>>> gRPC is still doing things the right way -- in that the framework defines 
>>> an interface I (the framework API consumer) then implements.  IOW: I don't 
>>> see that as a "java style interface" (where the interface defines the API 
>>> contract).
>>>
>>> I suspect you and I are saything the same thing.
>>>
>>> t.
>>>
>>> On Monday, August 8, 2022 at 12:51:29 PM UTC-7 bse...@computer.org 
>>> wrote:
>>>
 On Mon, Aug 8, 2022 at 12:51 PM Tim Peoples  
 wrote:

> I don't necessarily consider the "multiple implementations" case as 
> being truly preemptive -- if there really are multiple implementations 
> (e.g. the "hash" package from the standard library).
>
> I'm much more concerned about interfaces that are defined by an API 
> producer -- for one and only one impl -- and then adding a bunch of extra 
> (often autogenerated) code to deal with that.
>

 Like a gRPC client/server, or auto-generated swagger client/server?

 I've had many instances where such an auto-generated client had to be 
 passed down components that have no knowledge of those services. Writing 
 such components using interfaces 

Re: [go-nuts] Preemptive interfaces in Go

2022-08-09 Thread roger peppe
One significant argument against preemptive interfaces is that you can't
add a method to an interface type without breaking compatibility.

Also, an interface is significantly less amenable to static analysis
because it's not certain where a method call is implemented.

One concern I have about the current generics implementation is that it
does not infer types for generic interfaces, which increases the likelihood
that people will return preemptive interfaces rather than force people to
mention the type parameters explicitly (see
https://github.com/golang/go/issues/41176)

On Tue, 9 Aug 2022, 16:32 burak serdar,  wrote:

>
>
> On Mon, Aug 8, 2022 at 11:27 PM Henry  wrote:
>
>> I am sure that many of us have been on that journey. After using Go for
>> some time, we discover some practices that are not necessarily in agreement
>> with the existing "adages" but effectively solve our problems.
>>
>> For me, if the data type is mutable, I prefer returning interfaces. It
>> would be something like this:
>> ```
>> type Student interface {
>>//...
>> }
>>
>> type studentImpl struct {
>>//...
>> }
>>
>> func NewStudent(id string) Student {
>>return {
>>   //...
>>}
>> }
>> ```
>> There is a bit of history why I use this approach. For a struct with a
>> mutex, I wanted to ensure that the user did not accidentally copy the
>> struct. Nowadays we have *go vet* to give us a warning, but this was
>> before *go vet* had this functionality. So, I return a pointer to the
>> struct and hide it behind an interface. That way, it hides the
>> implementation details from the user and the user can pass the object
>> around without knowing whether it has a mutex or not.
>>
>> And then I ended up with some *constructors* returning structs and some
>> returning interfaces. To ensure consistency, my colleagues and I decided to
>> return interfaces for all mutable objects. For immutable objects, we return
>> structs.
>>
>> The nice thing about this approach is that it makes the syntax a lot
>> cleaner as you have to deal with fewer pointers.
>> ```
>> //instead of this
>> func Update(student *Student) {
>>   //...
>> }
>> func UpdateMany(students []*Student){
>>   //...
>> }
>>
>> //now you have this
>> func Update(student Student) {
>>   //...
>> }
>> func UpdateMany(students []Student){
>>   //...
>> }
>> ```
>> Some members in the team came from higher level languages and they found
>> working with pointers a bit awkward, so we made some accommodation for
>> them.
>>
>
>> There are times when I need to *upgrade* some of these mutable objects,
>> and this approach has proven to be quite flexible. It also plays nicely
>> with code generators.
>>
>> Some people may disagree with this approach, but I have been using it
>> ever since: return interface for mutable objects, return structs for
>> immutable objects.
>>
>
> I am one of those who disagrees. I have not seen any benefit from having
> interfaces for data objects other than making other developers happy. In my
> opinion, this amounts to emulating another language in Go. There are cases
> where this might make sense, but as a general principle, I think it should
> be avoided. Data is data. There are no implementation details to hide.
>
>
>
>> On Tuesday, August 9, 2022 at 3:09:10 AM UTC+7 t...@timpeoples.com wrote:
>>
>>> I can't speak to the *auto-generated swagger client* case but I believe
>>> gRPC is still doing things the right way -- in that the framework defines
>>> an interface I (the framework API consumer) then implements.  IOW: I don't
>>> see that as a "java style interface" (where the interface defines the API
>>> contract).
>>>
>>> I suspect you and I are saything the same thing.
>>>
>>> t.
>>>
>>> On Monday, August 8, 2022 at 12:51:29 PM UTC-7 bse...@computer.org
>>> wrote:
>>>
 On Mon, Aug 8, 2022 at 12:51 PM Tim Peoples 
 wrote:

> I don't necessarily consider the "multiple implementations" case as
> being truly preemptive -- if there really are multiple implementations
> (e.g. the "hash" package from the standard library).
>
> I'm much more concerned about interfaces that are defined by an API
> producer -- for one and only one impl -- and then adding a bunch of extra
> (often autogenerated) code to deal with that.
>

 Like a gRPC client/server, or auto-generated swagger client/server?

 I've had many instances where such an auto-generated client had to be
 passed down components that have no knowledge of those services. Writing
 such components using interfaces declaring only parts of those service
 implementations have benefits. An example that I can think of is an
 audit-trail service that deals with recording transaction metadata, looking
 them up, etc. It makes sense to write components that use only the writer
 part of that service, instead of requiring the whole thing. It makes
 writing tests easier. It lets you decouple services better, add
 

Re: [go-nuts] Preemptive interfaces in Go

2022-08-09 Thread burak serdar
On Mon, Aug 8, 2022 at 11:27 PM Henry  wrote:

> I am sure that many of us have been on that journey. After using Go for
> some time, we discover some practices that are not necessarily in agreement
> with the existing "adages" but effectively solve our problems.
>
> For me, if the data type is mutable, I prefer returning interfaces. It
> would be something like this:
> ```
> type Student interface {
>//...
> }
>
> type studentImpl struct {
>//...
> }
>
> func NewStudent(id string) Student {
>return {
>   //...
>}
> }
> ```
> There is a bit of history why I use this approach. For a struct with a
> mutex, I wanted to ensure that the user did not accidentally copy the
> struct. Nowadays we have *go vet* to give us a warning, but this was
> before *go vet* had this functionality. So, I return a pointer to the
> struct and hide it behind an interface. That way, it hides the
> implementation details from the user and the user can pass the object
> around without knowing whether it has a mutex or not.
>
> And then I ended up with some *constructors* returning structs and some
> returning interfaces. To ensure consistency, my colleagues and I decided to
> return interfaces for all mutable objects. For immutable objects, we return
> structs.
>
> The nice thing about this approach is that it makes the syntax a lot
> cleaner as you have to deal with fewer pointers.
> ```
> //instead of this
> func Update(student *Student) {
>   //...
> }
> func UpdateMany(students []*Student){
>   //...
> }
>
> //now you have this
> func Update(student Student) {
>   //...
> }
> func UpdateMany(students []Student){
>   //...
> }
> ```
> Some members in the team came from higher level languages and they found
> working with pointers a bit awkward, so we made some accommodation for
> them.
>

> There are times when I need to *upgrade* some of these mutable objects,
> and this approach has proven to be quite flexible. It also plays nicely
> with code generators.
>
> Some people may disagree with this approach, but I have been using it ever
> since: return interface for mutable objects, return structs for immutable
> objects.
>

I am one of those who disagrees. I have not seen any benefit from having
interfaces for data objects other than making other developers happy. In my
opinion, this amounts to emulating another language in Go. There are cases
where this might make sense, but as a general principle, I think it should
be avoided. Data is data. There are no implementation details to hide.



> On Tuesday, August 9, 2022 at 3:09:10 AM UTC+7 t...@timpeoples.com wrote:
>
>> I can't speak to the *auto-generated swagger client* case but I believe
>> gRPC is still doing things the right way -- in that the framework defines
>> an interface I (the framework API consumer) then implements.  IOW: I don't
>> see that as a "java style interface" (where the interface defines the API
>> contract).
>>
>> I suspect you and I are saything the same thing.
>>
>> t.
>>
>> On Monday, August 8, 2022 at 12:51:29 PM UTC-7 bse...@computer.org wrote:
>>
>>> On Mon, Aug 8, 2022 at 12:51 PM Tim Peoples  wrote:
>>>
 I don't necessarily consider the "multiple implementations" case as
 being truly preemptive -- if there really are multiple implementations
 (e.g. the "hash" package from the standard library).

 I'm much more concerned about interfaces that are defined by an API
 producer -- for one and only one impl -- and then adding a bunch of extra
 (often autogenerated) code to deal with that.

>>>
>>> Like a gRPC client/server, or auto-generated swagger client/server?
>>>
>>> I've had many instances where such an auto-generated client had to be
>>> passed down components that have no knowledge of those services. Writing
>>> such components using interfaces declaring only parts of those service
>>> implementations have benefits. An example that I can think of is an
>>> audit-trail service that deals with recording transaction metadata, looking
>>> them up, etc. It makes sense to write components that use only the writer
>>> part of that service, instead of requiring the whole thing. It makes
>>> writing tests easier. It lets you decouple services better, add
>>> adapters/interceptors etc.
>>>
>>>

 t.

 On Monday, August 8, 2022 at 11:02:31 AM UTC-7 bse...@computer.org
 wrote:

> On Mon, Aug 8, 2022 at 11:17 AM Tim Peoples 
> wrote:
>
>>
>> For years I've read the old adage, "Accept interfaces, return
>> structs" and have spent years working to instill this understanding among
>> my colleagues. I gathered a great many skills while learning Go (and
>> acquiring readability)  back in the day -- and one of the strongest of
>> those is the idea that interfaces should be defined by their consumer
>> instead of an API producer -- but I've now been away from Google longer
>> than I was there and I'm beginning to suspect that the general consensus

[go-nuts] Re: Generics types not inferred correctly on type switch cases

2022-08-09 Thread Brian Candler
I still assert that your problem is not really anything to do with 
generics, because you'd find the same problem even if you hard-coded to 
float32 and didn't have any generics at all (try it). I also don't 
understand what you're proposing to change.

The main thing you seem to be concerned about is slice literals: that is, 
having to specify the type conversion when assigning values to []any. Note 
that any is an *interface* type, and so each element in the slice a box 
which holds a value whose type is only known at runtime: a (type, value) 
pair if you like.

A little thought should make it clear that in general it's not possible to 
do what you want in slice literals automatically. Suppose you had:

type A float32
type B float32
type C float32
type Z interface {
// some interface which is implemented by A and B and C
}

This works fine:

v1 := []A{1.0, 2.0, 3.0}

because A is a concrete type which is assignable from float32, and each 
element is assigned individually, so you don't need to write A(1.0) etc.

But using an interface type it obviously can't work:

v2 := []Z{1.0, 2.0, 3.0}  // ???

There are multiple types which implement Z (but float64 doesn't).  For 
example, how would it know whether the first value needs to be A(1.0), 
B(1.0) or C(1.0) ??  It can't.
See https://go.dev/play/p/nZKAolK4orh

Therefore, you have to explicitly convert each value to one which is 
compatible with (implements) interface Z.

Now, if you write

v2 := []any{1.0, 2.0, 3.0}

That will work, because 1.0 is a float64 by default, and float64 implements 
"any" (as all types do - it's the empty interface). But the float64 value 
gets boxed directly. Again, it won't convert it into an A or a B or a C 
value (or a float32 or anything else) without being told to do so, because 
it has no way of inferring this is what you want.  That's exactly the issue 
you're seeing.


On Tuesday, 9 August 2022 at 13:55:29 UTC+1 esi...@gmail.com wrote:

>
> What you appear to be after is a recursive relationship with union types: 
> "S is a slice type, each of whose elements are either of type S or some 
> type T". 
>
> Exactly this is what I'm trying to emphasize: since I'm opting to use the 
> explicit generic type inference, I would expect the type system to "detect" 
> the type I've defined as a generic type constraint even though the type 
> parameter is not strictly related to the T type constraint. If I'd be using 
> baseFlatten[T any](acc []T, slice T) ([]T, error) this won't be an issue, 
> but in this case I'd be forced to use float32 instead of any, which I would 
> like to avoid. So I'm out of ideas how this situation could be overcome 
> without explicitly defining the parameter's type. Maybe posting a proposal? 
> On Tuesday, August 9, 2022 at 2:16:05 PM UTC+3 Brian Candler wrote:
>
>> Example of type inference in action:
>> https://go.dev/play/p/pL7_8zaFIZN
>>
>> What you appear to be after is a recursive relationship with union types: 
>> "S is a slice type, each of whose elements are either of type S or some 
>> type T".  AFAIK Go's type system isn't rich enough for that: hence for 
>> heterogenous slices you're stuck with interface types (including "any", 
>> which is a shortcut for the empty interface, "interface {}")
>>
>> On Tuesday, 9 August 2022 at 11:53:11 UTC+1 Brian Candler wrote:
>>
>>> As far as I can see, the problem isn't anything to do with generics, but 
>>> with this line alone:
>>> input := []any{[]float32{1.0, 2.0}, 1.1}
>>>
>>> See:
>>> https://go.dev/play/p/gUqmUb-Vs_y
>>>
>>> You've made a slice with mixed elements.  One happens to be a slice of 
>>> float32's, and the other happens to be a float64.  Go has no idea that 
>>> you'd prefer the second to be a float32.
>>>
>>> Generic type inference only happens locally at a function call site; it 
>>> doesn't propagate backwards to the rest of your program.  Furthermore:
>>>
>>> 1. When explicitly invoking Flatten[float32](...) you've bypassed the 
>>> type inference, by forcing type T to be float32
>>> 2. Type inference isn't possible for this function, because your 
>>> function's argument (slice any) does not relate to any of the generic type 
>>> parameters (i.e. it doesn't make use of "T")
>>>
>>> On Tuesday, 9 August 2022 at 11:08:42 UTC+1 esi...@gmail.com wrote:
>>>
 Let's consider the following situation:

 I have generic function which can accepts function arguments defined as 
 any. Now if want to know exactly the type of function arguments I have to 
 use the reflect package. The problem is that although on function 
 invocation we can explicitly define the type constraints, these are not 
 taken into consideration when we are matching the concrete types using the 
 reflect type switch.

 So let's say, that I have the following function:

 // Flatten flattens the slice all the way to the deepest nesting level.
 func Flatten[T any](slice any) ([]T, error) {
 return 

[go-nuts] Re: Generics types not inferred correctly on type switch cases

2022-08-09 Thread Endre Simo

What you appear to be after is a recursive relationship with union types: 
"S is a slice type, each of whose elements are either of type S or some 
type T". 

Exactly this is what I'm trying to emphasize: since I'm opting to use the 
explicit generic type inference, I would expect the type system to "detect" 
the type I've defined as a generic type constraint even though the type 
parameter is not strictly related to the T type constraint. If I'd be using 
baseFlatten[T any](acc []T, slice T) ([]T, error) this won't be an issue, 
but in this case I'd be forced to use float32 instead of any, which I would 
like to avoid. So I'm out of ideas how this situation could be overcome 
without explicitly defining the parameter's type. Maybe posting a proposal? 
On Tuesday, August 9, 2022 at 2:16:05 PM UTC+3 Brian Candler wrote:

> Example of type inference in action:
> https://go.dev/play/p/pL7_8zaFIZN
>
> What you appear to be after is a recursive relationship with union types: 
> "S is a slice type, each of whose elements are either of type S or some 
> type T".  AFAIK Go's type system isn't rich enough for that: hence for 
> heterogenous slices you're stuck with interface types (including "any", 
> which is a shortcut for the empty interface, "interface {}")
>
> On Tuesday, 9 August 2022 at 11:53:11 UTC+1 Brian Candler wrote:
>
>> As far as I can see, the problem isn't anything to do with generics, but 
>> with this line alone:
>> input := []any{[]float32{1.0, 2.0}, 1.1}
>>
>> See:
>> https://go.dev/play/p/gUqmUb-Vs_y
>>
>> You've made a slice with mixed elements.  One happens to be a slice of 
>> float32's, and the other happens to be a float64.  Go has no idea that 
>> you'd prefer the second to be a float32.
>>
>> Generic type inference only happens locally at a function call site; it 
>> doesn't propagate backwards to the rest of your program.  Furthermore:
>>
>> 1. When explicitly invoking Flatten[float32](...) you've bypassed the 
>> type inference, by forcing type T to be float32
>> 2. Type inference isn't possible for this function, because your 
>> function's argument (slice any) does not relate to any of the generic type 
>> parameters (i.e. it doesn't make use of "T")
>>
>> On Tuesday, 9 August 2022 at 11:08:42 UTC+1 esi...@gmail.com wrote:
>>
>>> Let's consider the following situation:
>>>
>>> I have generic function which can accepts function arguments defined as 
>>> any. Now if want to know exactly the type of function arguments I have to 
>>> use the reflect package. The problem is that although on function 
>>> invocation we can explicitly define the type constraints, these are not 
>>> taken into consideration when we are matching the concrete types using the 
>>> reflect type switch.
>>>
>>> So let's say, that I have the following function:
>>>
>>> // Flatten flattens the slice all the way to the deepest nesting level.
>>> func Flatten[T any](slice any) ([]T, error) {
>>> return baseFlatten([]T{}, slice)
>>> }
>>>
>>> func baseFlatten[T any](acc []T, slice any) ([]T, error) {
>>> var err error
>>>
>>> switch v := any(slice).(type) {
>>> case T:
>>> acc = append(acc, v)
>>> case []T:
>>> acc = append(acc, v...)
>>> case []any:
>>> for _, sv := range v {
>>> acc, err = baseFlatten(acc, sv)
>>> if err != nil {
>>> return nil, errors.New("flattening 
>>> error")
>>> }
>>> }
>>> default:
>>> return nil, errors.New("flattening error")
>>> }
>>>
>>> return acc, nil
>>> }
>>>
>>> I would expect that calling the Flatten method in the following way, the 
>>> types to be inferred correctly and the input values to be recognized as 
>>> float32 and NOT float64. Because otherwise I don't see the reason to use an 
>>> explicit type constraint on function invocation.  
>>>
>>> input := []any{[]float32{1.0, 2.0}, 1.1}
>>> result, err := Flatten[float32](input)
>>>
>>> The generic type switch cases as defined in the proposal (
>>> https://go.googlesource.com/proposal/+/HEAD/design/43651-type-parameters.md#generic-types-as-type-switch-cases)
>>>  
>>> is not intuitive, at least for me.   
>>>
>>> Now the question is how could I infer correctly the parameter type 
>>> without explicitly defining the value 1.1 as float32 like this:
>>>
>>> input := []any{[]float32{1.0, 2.0}, float32(1.1)}
>>> result, err := Flatten[float32](input)
>>>
>>

-- 
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/c052db47-96a3-4e2c-8db4-d29784988599n%40googlegroups.com.


[go-nuts] Slightly confusing error with type parameters

2022-08-09 Thread Brian Candler
I don't know if there's much that can be done about this, but thought I'd 
mention it anyway.

Suppose you're trying to make a parameterised type, like
type Elem[T any] blah...
ex: https://go.dev/play/p/1tS1SCxTFP-

but you forget the "any", and just write
type Elem[T] blah...
ex: https://go.dev/play/p/uig7iXsgZeU

The error message, and the automatic reformatting you get, is a bit 
confusing:

type Elem [T]blah...
undeclared name T for array length

I guess this is a fundamental ambiguity in the language.  However, if an 
array length expression comes directly after a type name, *and* the array 
length itself consists of a single undeclared name, I wonder if the error 
message could give more of a hint?

undeclared name T for array length, or missing constraint for type 
parameter (e.g. "T any")

When there are multiple type parameters, it's fine:
https://go.dev/play/p/_gmrdPzoWCD

-- 
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/58763cca-622f-45d4-9c6c-9dfdbce2f623n%40googlegroups.com.


[go-nuts] Re: Generics types not inferred correctly on type switch cases

2022-08-09 Thread Brian Candler
Example of type inference in action:
https://go.dev/play/p/pL7_8zaFIZN

What you appear to be after is a recursive relationship with union types: 
"S is a slice type, each of whose elements are either of type S or some 
type T".  AFAIK Go's type system isn't rich enough for that: hence for 
heterogenous slices you're stuck with interface types (including "any", 
which is a shortcut for the empty interface, "interface {}")

On Tuesday, 9 August 2022 at 11:53:11 UTC+1 Brian Candler wrote:

> As far as I can see, the problem isn't anything to do with generics, but 
> with this line alone:
> input := []any{[]float32{1.0, 2.0}, 1.1}
>
> See:
> https://go.dev/play/p/gUqmUb-Vs_y
>
> You've made a slice with mixed elements.  One happens to be a slice of 
> float32's, and the other happens to be a float64.  Go has no idea that 
> you'd prefer the second to be a float32.
>
> Generic type inference only happens locally at a function call site; it 
> doesn't propagate backwards to the rest of your program.  Furthermore:
>
> 1. When explicitly invoking Flatten[float32](...) you've bypassed the type 
> inference, by forcing type T to be float32
> 2. Type inference isn't possible for this function, because your 
> function's argument (slice any) does not relate to any of the generic type 
> parameters (i.e. it doesn't make use of "T")
>
> On Tuesday, 9 August 2022 at 11:08:42 UTC+1 esi...@gmail.com wrote:
>
>> Let's consider the following situation:
>>
>> I have generic function which can accepts function arguments defined as 
>> any. Now if want to know exactly the type of function arguments I have to 
>> use the reflect package. The problem is that although on function 
>> invocation we can explicitly define the type constraints, these are not 
>> taken into consideration when we are matching the concrete types using the 
>> reflect type switch.
>>
>> So let's say, that I have the following function:
>>
>> // Flatten flattens the slice all the way to the deepest nesting level.
>> func Flatten[T any](slice any) ([]T, error) {
>> return baseFlatten([]T{}, slice)
>> }
>>
>> func baseFlatten[T any](acc []T, slice any) ([]T, error) {
>> var err error
>>
>> switch v := any(slice).(type) {
>> case T:
>> acc = append(acc, v)
>> case []T:
>> acc = append(acc, v...)
>> case []any:
>> for _, sv := range v {
>> acc, err = baseFlatten(acc, sv)
>> if err != nil {
>> return nil, errors.New("flattening error")
>> }
>> }
>> default:
>> return nil, errors.New("flattening error")
>> }
>>
>> return acc, nil
>> }
>>
>> I would expect that calling the Flatten method in the following way, the 
>> types to be inferred correctly and the input values to be recognized as 
>> float32 and NOT float64. Because otherwise I don't see the reason to use an 
>> explicit type constraint on function invocation.  
>>
>> input := []any{[]float32{1.0, 2.0}, 1.1}
>> result, err := Flatten[float32](input)
>>
>> The generic type switch cases as defined in the proposal (
>> https://go.googlesource.com/proposal/+/HEAD/design/43651-type-parameters.md#generic-types-as-type-switch-cases)
>>  
>> is not intuitive, at least for me.   
>>
>> Now the question is how could I infer correctly the parameter type 
>> without explicitly defining the value 1.1 as float32 like this:
>>
>> input := []any{[]float32{1.0, 2.0}, float32(1.1)}
>> result, err := Flatten[float32](input)
>>
>

-- 
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/f9db7549-491f-4816-a0bc-a8e9ddb8952en%40googlegroups.com.


[go-nuts] Re: Generics types not inferred correctly on type switch cases

2022-08-09 Thread Brian Candler
As far as I can see, the problem isn't anything to do with generics, but 
with this line alone:
input := []any{[]float32{1.0, 2.0}, 1.1}

See:
https://go.dev/play/p/gUqmUb-Vs_y

You've made a slice with mixed elements.  One happens to be a slice of 
float32's, and the other happens to be a float64.  Go has no idea that 
you'd prefer the second to be a float32.

Generic type inference only happens locally at a function call site; it 
doesn't propagate backwards to the rest of your program.  Furthermore:

1. When explicitly invoking Flatten[float32](...) you've bypassed the type 
inference, by forcing type T to be float32
2. Type inference isn't possible for this function, because your function's 
argument (slice any) does not relate to any of the generic type parameters 
(i.e. it doesn't make use of "T")

On Tuesday, 9 August 2022 at 11:08:42 UTC+1 esi...@gmail.com wrote:

> Let's consider the following situation:
>
> I have generic function which can accepts function arguments defined as 
> any. Now if want to know exactly the type of function arguments I have to 
> use the reflect package. The problem is that although on function 
> invocation we can explicitly define the type constraints, these are not 
> taken into consideration when we are matching the concrete types using the 
> reflect type switch.
>
> So let's say, that I have the following function:
>
> // Flatten flattens the slice all the way to the deepest nesting level.
> func Flatten[T any](slice any) ([]T, error) {
> return baseFlatten([]T{}, slice)
> }
>
> func baseFlatten[T any](acc []T, slice any) ([]T, error) {
> var err error
>
> switch v := any(slice).(type) {
> case T:
> acc = append(acc, v)
> case []T:
> acc = append(acc, v...)
> case []any:
> for _, sv := range v {
> acc, err = baseFlatten(acc, sv)
> if err != nil {
> return nil, errors.New("flattening error")
> }
> }
> default:
> return nil, errors.New("flattening error")
> }
>
> return acc, nil
> }
>
> I would expect that calling the Flatten method in the following way, the 
> types to be inferred correctly and the input values to be recognized as 
> float32 and NOT float64. Because otherwise I don't see the reason to use an 
> explicit type constraint on function invocation.  
>
> input := []any{[]float32{1.0, 2.0}, 1.1}
> result, err := Flatten[float32](input)
>
> The generic type switch cases as defined in the proposal (
> https://go.googlesource.com/proposal/+/HEAD/design/43651-type-parameters.md#generic-types-as-type-switch-cases)
>  
> is not intuitive, at least for me.   
>
> Now the question is how could I infer correctly the parameter type without 
> explicitly defining the value 1.1 as float32 like this:
>
> input := []any{[]float32{1.0, 2.0}, float32(1.1)}
> result, err := Flatten[float32](input)
>

-- 
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/cd4c58bc-5a0a-45f1-a69f-89d0ad73f900n%40googlegroups.com.


[go-nuts] Generics types not inferred correctly on type switch cases

2022-08-09 Thread Endre Simo
Let's consider the following situation:

I have generic function which can accepts function arguments defined as 
any. Now if want to know exactly the type of function arguments I have to 
use the reflect package. The problem is that although on function 
invocation we can explicitly define the type constraints, these are not 
taken into consideration when we are matching the concrete types using the 
reflect type switch.

So let's say, that I have the following function:

// Flatten flattens the slice all the way to the deepest nesting level.
func Flatten[T any](slice any) ([]T, error) {
return baseFlatten([]T{}, slice)
}

func baseFlatten[T any](acc []T, slice any) ([]T, error) {
var err error

switch v := any(slice).(type) {
case T:
acc = append(acc, v)
case []T:
acc = append(acc, v...)
case []any:
for _, sv := range v {
acc, err = baseFlatten(acc, sv)
if err != nil {
return nil, errors.New("flattening error")
}
}
default:
return nil, errors.New("flattening error")
}

return acc, nil
}

I would expect that calling the Flatten method in the following way, the 
types to be inferred correctly and the input values to be recognized as 
float32 and NOT float64. Because otherwise I don't see the reason to use an 
explicit type constraint on function invocation.  

input := []any{[]float32{1.0, 2.0}, 1.1}
result, err := Flatten[float32](input)

The generic type switch cases as defined in the proposal (
https://go.googlesource.com/proposal/+/HEAD/design/43651-type-parameters.md#generic-types-as-type-switch-cases)
 
is not intuitive, at least for me.   

Now the question is how could I infer correctly the parameter type without 
explicitly defining the value 1.1 as float32 like this:

input := []any{[]float32{1.0, 2.0}, float32(1.1)}
result, err := Flatten[float32](input)

-- 
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/9cd2161f-da81-4bcf-9c69-442259d8d2ebn%40googlegroups.com.


[go-nuts] Tuy

2022-08-09 Thread Endre Simo
Let's consider the following situation:

I have generic function which can accepts function arguments defined as 
any. Now if want to know exactly the type of function arguments I have to 
use the reflect package. The problem is that although on function 
invocation we can explicitly define the type constraints, these are not 
taken into consideration when we are matching the concrete types using the 
reflect type switch.

So let's say, that I have the following function:

// Flatten flattens the slice all the way to the deepest nesting level.
func Flatten[T any](slice any) ([]T, error) {
return baseFlatten([]T{}, slice)
}

func baseFlatten[T any](acc []T, slice any) ([]T, error) {
var err error

switch v := any(slice).(type) {
case T:
acc = append(acc, v)
case []T:
acc = append(acc, v...)
case []any:
for _, sv := range v {
acc, err = baseFlatten(acc, sv)
if err != nil {
return nil, errors.New("flattening error")
}
}
default:
return nil, errors.New("flattening error")
}

return acc, nil
}

I would expect that calling the Flatten method in the following way, the 
types to be inferred correctly and the input values to be recognized as 
float32 and NOT float64. Because otherwise I don't see the reason to use an 
explicit type constraint on function invocation.  

input := []any{[]float32{1.0, 2.0}, 1.1}
result, err := Flatten[float32](input)

The generic type switch cases as defined in the proposal 
(https://go.googlesource.com/proposal/+/HEAD/design/43651-type-parameters.md#generic-types-as-type-switch-cases)
 
is not intuitive, at least for me.   

Now the question is how could I infer correctly the parameter type without 
explicitly defining the value 1.1 as float32 like this:

input := []any{[]float32{1.0, 2.0}, float32(1.1)}
result, err := Flatten[float32](input)

-- 
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/dd91b2f0-d9e1-45e7-b8a1-c00f2a36825an%40googlegroups.com.