Sorry, I accidentally sent the previous message before it was complete. Please ignore that one.
On Tue, Aug 11, 2020 at 8:31 PM Ian Lance Taylor <i...@golang.org> wrote: > To me the point of your String example is: Go already has various ways > of writing generic code. You can use interfaces, methods, and type > assertions. You can use reflection. The generics design draft adds > another way: parametric polymorphism. The other point is that writing functions or types that work with both builtin and user-defined types is not just good for reducing duplication in the implementation. It also simplifies the use of those functions. I imagine most people don't really care how much effort was required to write fmt.Print. But they would object if asked to write fmt.PrintB(1, " ") fmt.PrintS(big.NewInt(2)) fmt.PrintB(" ", 3) instead of fmt.Print(1, big.NewInt(2), 3) > > A better example might be a function that finds the roots of a > > polynomial with real-valued coefficients ... > Thanks, it's an interesting example. big.Float isn't really the same > as float32 or float64. For example, big.Float doesn't provide any way > to write "a = b + c + d". You can only write, in effect, "a = b + c; > a += d". And big.Float doesn't have == != < <= >= >. Instead it has > expressions like "a.Cmp(b) == 0". So if you want to write a generic > function that works on float32/float64/big.Float, you're going to need > adaptors no matter what. You can't avoid them. > > So in practice I don't think we're going to write the root-finding > function using parametric polymorphism at all. I think we're going to > write it using an interface type. I think we'll need an interface type and parametric polymorphism. type Float interface{ Add(Float) Float } doesn't have the right semantics. > And I think we're going to base > that interface type on big.Float. Then we need to write an > implementation of that interface for float32/float64. And that > implementation is likely to use parametric polymorphism so that we > only have to write it once to handle both float32 and float64. And > for that implementation, type lists work fine. I tried out a few different implementations, evaluating the polynomial instead of finding roots, at https://go2goplay.golang.org/p/g8bPHdg5iMd . As far as I can tell, there is no sensible way to do it with an interface that is implemented directly by *big.Float; we need to wrap *big.Float in a wrapper type in any case. One can write type Float[type F] interface { Add(F, F) F } func Sum[type F Float[F]](a, b F) F { var sum F return sum.Add(a, b) } but this causes a segmentation violation in the call to sum.Add because sum is nil. > > What you're looking for here, I think, is a case where there is a type > > A that implements methods that are isomorphic to the basic arithmetic > > and/or comparison operators. And then you want an algorithm that can > > be used with that type, as well as with the builtin types. For such a > > case, it might be nice to be able to write that algorithm only once. The big.Float example highlights the danger of looking for examples in current Go. Once we have generics, we can write functions that implement algorithms such as this. Having those algorithms will create an incentive for new types to be given methods that satisfy the interfaces used by the algorithms. But without that incentive, people write their types in whatever way is most convenient for them, with no eye to standardization. I think the more important thing is whether the general semantics match (these are addable things, not the details of how they are added). Once we have generics, there may be some standardization in how the methods actually work. Having said that, note that image.Point has Add and Sub methods similar to + and -. And https://godoc.org/lukechampine.com/uint128 has several methods corresponding to builtin operators. I have no idea how widely used it is. > > And, of course, with the current design draft, we can write the > > algorithm only once, provided we write it using methods. We will use > > the methods defined by the type A. And we will need an adapter for > > builtin types to implement those methods. And that adaptor can be > > written using type lists. >From my tests in https://go2goplay.golang.org/p/g8bPHdg5iMd , the way I would prefer involves a general function to do the operation, and that accepts one or more adaptors to specify the primitive operations. The functions for the builtin and user-defined cases would call that general function. > Now, I think your original argument is that in the future we might > decide to introduce operator methods. And we might add those operator > methods to A. And then we can write our algorithm using operators. > But we might have a chain of functions already built using (regular) > methods, and we won't be able to use those with operator methods, so > we will be discouraged from using operator methods. > > So first let me observe that that seems to me like a pretty long chain > of hypotheticals. Is there any type out there today that has methods > that correspond exactly to operators? There is a reason that > big.Float and friends don't have such methods: for any type that uses > pointers, they are inefficient. Is this possibility going to be a > real problem, or will it always be a hypothetical one? > > Second, let me observe that in this example it seems to me that we > will already have the adapter types that add methods to the builtin > types. So the question would be: if we add operator methods, can we > very easily shift those adaptor types from using type lists to using > operator methods instead? And it seems to me that the answer is yes. > If we have operator methods, we will be able to write interface types > with operator methods. And if we do that it would be very natural to > let the builtin types implement those interfaces. And, of course, we > can use those interface types as constraints. So we just have to > change the constraints that our adapter types use from type lists to > interface types with operator methods. And everything else will work. > So, sure, operator methods might lead to some code tweaks. But they > won't require a massive overhaul. > > Or so it seems to me. What am I missing? Looking at the polynomial examples I mentioned above, this seems mostly right. However, there will often be some other changes needed. For example, a generic function dealing with a type F that must be either float32 or float64 can use a plain 5 as a value of type F. If the function is converted to allow user-defined types with appropriate operators, then additional code will be needed to convert 5 to type F. Also, conversions may pose difficulties. > The particular topic here is whether adopting type lists today is > going to tie our hands in the future in a way that we don't anticipate > and that would be bad. It's possible of course, but it's not yet > clear to me that that must be so. It's not clear to me either; I just wanted to raise the possibility as something deserving thought. It now seems to me less likely to be a serious concern, but I'm still not certain. Thanks for your thoughts. -- 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/CAADvV_sEUcHB7Q_C860A_SMosKVMX%2BUZZm34x%2BOS3yVDqr%3Dw6A%40mail.gmail.com.