On Sunday, September 9, 2018 at 3:52:24 AM UTC-4, Ian Lance Taylor wrote:
>
> On Sat, Sep 8, 2018 at 7:53 PM, Jonathan Amsterdam
> <[email protected] <javascript:>> wrote:
> >> When is it important to not just express what operations are
> >> required for a type, but also to try to rule out some types?
>
> >
> > I think the short answer is: numeric code. That's when one thinks about
> > which types make sense for an algorithm, not just which operations.
> >
> > I'm not exactly sure why. It could be because Go doesn't support
> operator
> > overloading. In C++ with concepts, maybe the concept for writing some
> > generic math function wants operator+ and operator/, and no one cares
> that
> > that may result in nonsense for some implementations of those operators.
> >
> > In Go 1, we write numeric code with specific types: float64 and not
> int64,
> > for instance. Go insists on conversions between numeric types to keep
> us
> > out of trouble: you can't call func Average(a, b float64) on ints
> without
> > explicitly converting them. It seems natural to want to keep that
> precision
> > and safety, but generalize it.
> >
> > In Go 1, if we wanted to write a numeric algorithm that worked on, say,
> two
> > slices of floating-point numbers, we'd give it the signature func(x, y
> > []float64). If later we wanted to support float32, we'd have to copy the
> > code, or make the caller do an expensive conversion. The same problem
> would
> > happen if the caller defined their own floating-point type (e.g. type
> > MyFloat float64).
> >
> > I think I should be able to say, simply and clearly, that the slice
> element
> > can be any type whose underlying type is float32 or float64.
> Furthermore, I
> > don't want to specify the exact operations my implementation currently
> uses.
> > I may want to change the code to use "-" instead of "+" later, for
> reasons
> > of clarity or numerical stability. I shouldn't have to update the
> contract
> > for that.
> >
> > contract(t T) { t = 0.0 } might seem to restrict to the types I want,
> but it
> > doesn't; you can assign 0.0 to an integer type.
> >
> > contract(t T) { t = 0.5 } is wrong too. It includes complex types.
> >
> > contract(t T) { float64(t) } also works for ints, so no good.
> >
> > I believe the answer is
> >
> > contract(t T) { t = 0.5; float64(t) }
> >
> > but I'm not positive about that. Certainly, very few people who read
> that
> > will immediately understand that T is a type whose underlying type is a
> > float.
> >
> > (And I haven't done the second part, where I add all the valid operators
> for
> > floats.)
> >
> > Earlier you wrote
> >
> >> [T]o me it always seems quite clear which type arguments a contract
> allows
> > and excludes. It's exactly the set of types that type check
> > successfully. It's true that sometimes this can be a surprising type,
> > but to me that seems just like the fact that it can be surprising
> > which types implement an interface.
> >
> > I think this example, and the other ones given in this thread,
> demonstrate
> > that this is not "just like" the surprise of which types implement an
> > interface. It's more like solving a puzzle, as someone (Axel?) has said.
>
> It is like solving a puzzle because you are trying to figure out how
> to exclude some types. Why are you doing that? Why not just describe
> the types you accept, and not worry about other types? Just as we do
> for interface types?
>
I feel I'm just using types as I always do, to describe (approximately) the
valid
values for something. I don't use float64 for a counter, I use int. I
declare the name
field of a Person struct as string, not interface{}, even if I'm only going
to print it and
compare it for equality.
> I don't yet find your Average example to be convincing. Clearly if
> Average is going to take values of type T and return a value of type
> T, then if called on integers it is going to be a rounded value. That
> is inherent in the description.
Agreed, Average is a poor example. Take something where the answer will
always
be in floating-point, like standard deviation:
func Stdev(type T)(xs []T) float64
I might naively write this as
contract c(t T) { t + t; t - t; t/t; float64(t) }
func Stdev(type T c)(xs []T) float64 {
mean := Average(xs) // generalizing my Average in the obvious way
s := 0.0
for _, x := range xs {
d := x - mean
s += float64(d*d)
}
return math.Sqrt(s / float64(len(xs)-1))
}
For T = int the answers are a bit off, because the mean is truncated. It is
hard to find this bug by reading the code. The types, which normally give
you a clue, are absent (although the conversion float64(d*d) is suspicious).
Even if we agree that Average should work for ints, I still want to write
that explicitly in my contract.
> I don't see this as analogous to the
> fact that we require explicit type conversions between different
> numeric types.
>
Only in the general sense that it prevents errors that arise from
approximate
representations of numbers.
--
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 [email protected].
For more options, visit https://groups.google.com/d/optout.