Go Syntax and Human Failings

I wrote a Go program this morning. I thought of a super clever way to find the median of a sorted slice of float64 types. This clever way to do medians worked in the little program I wrote first, because I strictly follow Gall’s Law.

Finding the median of values in an even length array or slice means finding the two array indexes on either side of the “middle” of the array. For example, in a 10 element array, you want to average elements at indexes 4 and 5 (for languages with 0-indexed arrays).

My test programs used sorted slices with values like [0., 1., 2., 3., 4., 5., 6.] so I could see which indexes got used in the super clever median value calculated.

I put the median-finding-function to use in a larger program, where I used the sort package on a slice.

    sorted := sort.Float64Slice(unsortedData)

My super clever median calculating function gave weird values. The slice sometimes seemed to be sorted in reverse order, high to low. The median value returned by my function made no numerical sense.

Finally, I read more of the documentation than a glance at the output of go doc sort. I discovered a second function, sort.Float64s(). Miraculously, using the second function before my super clever median calculation caused it to return correct minimum, maximum and median values.

Why were there two functions to sort slices of float64? Because sort.Float64Slice is a type conversion, and sort.Float64s() is the function in that package that sorts a slice of float64. They both look like functions, they must have the same signature, the Go compiler won’t let you assign an incorrect type.

func Float64s(x []float64)
func Float64Slice(x []float64) Float64Slice

Float64s is an actual function, with human-written code. Float64Slice is a type. The GO compiler creates a type conversion for you if you write a named type that’s an “alias” for a built-in type. Package sort defines a type Float64Slice. The compiler-created type conversion looks exactly like a function, and the sort package (at first glance) looks like it has sorting functions for built-in types (Ints, Strings etc). By not reading carefully enough, I used a type conversion where I should have used a function.

This bug is inobvious because a type conversion is visually identical to a function or method call. That’s a rare defect in the Go programming language that does not appear in its immediate ancestor, C. C doesn’t have type conversions per se, but it does have casts:

    typdef double* Float64Slice;
    double *array;
    Float64Slice sorted;
    ...
    sorted = (Float64Slice)array;

Casts are visually quite distinctive, reflecting C’s evolution. The bug I wrote took too long to spot and fix because type conversions that look like functions intersects with how my mind processes the visual representation of the programming language.

The Clever Trick

func findMedian(data []float64) float64 {
    l := len(data)
    q := l / 2
    a := 1 - (l & 0x01)

    return (data[q-a] + data[q]) / 2.0
}

No if/then/else to distinguish calculations for odd or even counts of data.