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 > <jbams...@gmail.com <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 golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.