On Tue, Aug 11, 2020 at 4:19 PM Patrick Smith <pat42sm...@gmail.com> wrote: > > On Sun, Aug 9, 2020 at 6:29 PM Ian Lance Taylor <i...@golang.org> wrote: > > 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. > > This I disagree with; what you regard as inappropriate, I see as a > desirable goal. The question is whether there is a reasonable way to > achieve it, one for which the benefits outweigh the costs. > > Before I explain why I disagree, let me mention that sorting was > perhaps not the best example for me to have used. It is very common to > sort things in an order different than the default (e.g. integers in > reverse order), or to sort things that don't have a default order > (e.g. countries by area, population, or date of formation). For this > reason, sort functions in any language very often accept a comparison > function. > > A better example might be a function that finds the roots of a > polynomial with real-valued coefficients, where the coefficients are > taken from a type F that might be float32, float64, or a user-defined > type such as one based on big.Float. In this function, we want to use > the default arithmetic operations provided by F. It would be very > unusual to override those with a different choice of operations. > > But in the type lists approach, unless we write two distinct versions > of the function, we must use an adaptor type or object (or multiple > adaptor functions) to indicate to the function what arithmetic > operations to use. There is no way for the function to access these > operations otherwise (except reflection).
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. 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. 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. 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. 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? > > 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 Go does not have default argument values is, I think, merely a > distraction. > This is easily handled with multiple functions; one would like to write > > func SortBy[type T](s []T, less func(T, T) bool) { ... } > > func Sort[type T ...](s []T) { > SortBy(s, the_natural_order_on_T) > } > > where the constraint on T in Sort expresses that T must have a natural > ordering. Yes. One way to state my point is to note that you had to give the functions different names: Sort and SortBy. In C++ those two functions have the same name, where your Sort function is in effect SortBy with a default argument. 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 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. Thanks for the note. 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/CAOyqgcWAieOXNDQFZ2YB3xzMqv19duh-9PzSZLpnd9M4AZCbwg%40mail.gmail.com.