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. The trouble is that you can write

type Float[type F] interface { Add(F, F) F }

func Sum[type F Float[F]](a, b F) F {

}


>
>
> 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/CAADvV_v4E9VzLZKjhv3rXfwzyBfWWGFQhhM6nD3uf4xtthzTeg%40mail.gmail.com.

Reply via email to