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.

Reply via email to