On Fri, Aug 7, 2020 at 4:33 PM Patrick Smith <pat42sm...@gmail.com> wrote: > > I like the second draft for generics. It seems to me a large > simplification and improvement over the first draft. Considering just > the state of Go today, I would be quite happy with this, even if it's > not perfect. Thanks to Ian, Robert, and everyone else for their work > on this. > > Also, I would vote for square brackets over parentheses. > > But I do have concerns related to the future development of Go. In > particular, if we think it likely that a future version of Go will > allow operator overloading, then perhaps type lists are not the best > choice. > > To my mind, the biggest defect in the design draft is that we can't > write generic functions and types that work transparently with both > builtin and user-defined types (that do not inherit appropriate > behavior from an underlying builtin type). For example, we can't write > a > > func Min[type T ...](a, b T) T { ... } > > that works both when T is int and when T is > > type BigInt struct { i *big.Int } > > Instead, we would use workarounds such as writing two versions of Min, > or passing in an adaptor function or object; in the case of Min, a > comparison function. And that's OK, especially in an initial version > of generics. > > But generics would be significantly easier to use if we could write > functions that work on both builtin and user-defined types. The two > most likely candidates for allowing this seem to be operator > overloading (where BigInt might have a method named "<", "operator<", > or some such, that allows it to be used with the < operator) and > methods on builtin types (where int might be given a method named Less > with the same behavior as the < operator). Of course, other solutions > could be imagined, but I'll confine my speculations to those two. > > Now let's try to imagine how sorting slices might be implemented in > the standard library in various futures. Of course, the current sort > package would have to be kept and maintained for a long time. > > If Go2 implements the current draft with type lists, then we might add > a sort2 package containing something to: > > func SliceBy[type T](s []T, less(T, T) bool) { ... } > > type Ordered interface { // Copied from the draft > type int, int8, int16, int32, int64, > uint, uint8, uint16, uint32, uint64, uintptr, > float32, float64, > string > } > > func Slice(type T Ordered)(s []T) { > SliceBy(s, func(a, b T) bool { return a < b }) > } > > type Lesser[type T] interface { Less(T) bool } > > func SliceByLess(type T Lesser[T])(s []T) { > SliceBy(s, T.Less) > } > > All well and good. Now say time goes by and Go3 adds operator methods. > Nothing in the sort2 package expresses a unified sort using operator > methods, so we need a new package sort3: > > func SliceBy[type T](s []T, less(T, T) bool) { ... } > > type Lesser[type T] interface { <(T) bool } // Or whatever the syntax is. > > func Slice(type T Lesser)(s []T) { > SliceBy(s, func(a, b T) bool { return a < b }) > } > > (We might just add sort3.Lesser and sort3.Slice into the sort2 package > under different names, but I suspect the aim would be to eventually > deprecate sort2.) > > The effects will ripple through other code, both in and outside the > standard library. Suppose some Go2 code has a chain of generic > functions A calls B calls C calls D, where each exists in two > versions, one for builtin types and one for user-defined types, and > the two versions of D call sort2.Slice or sort2.SliceByLess. When Go3 > with operator methods arrives, if we want to unify these, we have to > write a third version of each of A, B, C, and D, where D calls > sort3.Slice. > > On the other hand, suppose Go2 has type lists and Go3 gives builtin > types methods corresponding to operators. Assuming the name Less is > used for <, sort2.SliceByLess now handles both builtin and > user-defined types, so we don't need a sort3 package. And in the ABCD > scenario, we can just keep the SliceByLess version of each, and > quietly let the sort2.Slice versions vanish as they become unused. > > [Important point===>] This means that if Go2 has type lists in > interfaces, there will be a strong incentive for Go3 to give builtin > types methods, even if we think that operator overloading is otherwise > a superior solution, because operator overloading will require much > more new code to be written. > > Instead of using type lists, suppose Go2 allowed interfaces to require > the presence of specific operators. Then the sort2 header might look > like this: > > func SliceBy[type T](s []T, less(T, T) bool) { ... } > > type Lesser[type T] interface { <(T) bool } > > func Slice(type T Lesser)(s []T) { > SliceBy(s, func(a, b T) bool { return a < b }) > } > > type MLesser[type T] interface { Less(T) bool } > > func SliceM(type T MLesser[T])(s []T) { > SliceBy(s, T.Less) > } > > (Note that the first part is identical to the previous sort3 header. > But in Go2 we also need MLesser and SliceM in order to handle > user-defined types.) > > This leaves us more easy options in Go3. If Go3 implements operator > overloading, then sort2.Slice now handles both builtin and > user-defined types, and code using sort2.SliceM can be allowed to > wither away. If Go3 implements an int.Less method, then sort2.SliceM > is now the good version, and code using sort2.Slice can be allowed to > wither away as it becomes unused. > > So maybe the alternative of allowing interfaces to require specific > operators deserves another look, to see if it's really not viable in > Go2. I would suggest that perhaps the initial version of generics need > not support all operators; maybe it's enough to support only those > that apply to numeric types (string should be accepted by interfaces > requiring +, <, and other comparison operators). Or maybe a slightly > larger subset of operators would do. > > As a final note - this post, like all speculations about the future, > is rather fuzzy. I realize that. Nevertheless, I think it is important > to realize that the choices we make now carry consequences for our > options after a few years' time.
Thanks for the detailed comment. I think the key statement in your argument is this one: > But generics would be significantly easier to use if we could write > functions that work on both builtin and user-defined types. The two > most likely candidates for allowing this seem to be operator > overloading (where BigInt might have a method named "<", "operator<", > or some such, that allows it to be used with the < operator) and > methods on builtin types (where int might be given a method named Less > with the same behavior as the < operator). Of course, other solutions > could be imagined, but I'll confine my speculations to those two. I think this is open to question. In C++, for example, std::sort takes an optional comparison class. In effect, the default if no comparison class is provided is to use operator<. That is a reasonable and appropriate choice for C++. But Go does not have function overloading and does not have default values for arguments (https://golang.org/doc/faq#overloading). So the natural way to write a sort function is to provide a comparison function. That is how sort.Slice works, for example. sort.Sort works differently because it needs three different functions, so they are passed in via a type rather than as an argument. In typical use, people will convert to that type when they call sort.Sort, so they are providing the required functions via a type conversion. If we accept this argument, then in Go it wouldn't be appropriate to write a single function that works on both builtin and user-defined types. Writing such a function would be relying on some sort of default comparison function, which is not the typical Go approach. So instead we need to ensure that it is very easy to pass a comparison function, regardless of whether you are using a builtin type or a user-defined type. And I think that that is already true. 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/CAOyqgcUPX_egw6gopFGbTjL5sGHb838jGrQfo5z8GMO3CwUzyQ%40mail.gmail.com.