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.

Reply via email to