Go Gaps

Go is a great and superbly simple language. It has a high degree of credibility in its competence because its progenitors worked tirelessly on achieving simple and clean precision.

But it’s not perfect.

  • There is no easy way to compare structs deeply without using reflect.DeepEqual or some hard work http://stackoverflow.com/questions/24534072/golang-how-to-compare-struct-slice-map-is-equal
  • I really would like some single-assignment style of immutability.
  • Channel selection doesn’t have boolean guards, unlike Occam. This is a shame when these are essentially easy to implement and greatly simplify the code needed to write stateful actors (processes in CSP-speak).
  • Channel direction uses arrow syntax. This is superficially attractive, but one might well argue that this is less simple than using the ? and ! operations of CSP (also used in Occam). These are distinct from each other, so are quicker to read. Why did Go invent new and less-clear syntax?
  • Optional values as a language feature.
  • User-definable arithmetic operators.

The last two are debatable issues; so that’s what I shall focus on in what follows.

Optional values as a language feature

Currently appearing in maps, which can return an optional value with a flag. Example …TODO…

Also Context includes

// Deadline returns the time when this Context will be canceled, if any.
Deadline() (deadline time.Time, ok bool)

There’s a clear need for an optional result there.

User-definable arithmetic operators

It was probably not hard to exclude user-definable arithmetic operators out of Go. It’s one of those nice-to-haves that might have delayed the language getting to where it is so rapidly. The case against them probably revolves around the extra compiler and grammar complexity and the risk of introducing a feature which then gets abused, to the detriment of Go’s credibility as a simple highly-predictable language.

However, there is still a case for user-definable arithmetic operators. Take for example the an inconsistency in the API: rational numbers are defined package math/big, whereas complex numbers are built-in primitives. What if I want primitive-style rational numbers (which might be ideal for currency values)? Or what about unbounded-length complex numbers? I have to write my own. Hmm.

Why does the rational number API feel so wooden? Here’s a trivial example:

price := big.NewRat(80, 1)
vatAmount := big.NewRat(0, 1)
gross := big.NewRat(0, 1)
vat := big.NewRat(20, 100) // 20%

vatAmount = vatAmount.Mul(price, vat)
gross = gross.Add(price, vatAmount)
fmt.Printf("Net %v, vat %v, gross %v\n", price, vatAmount, gross)

when all I really wanted was

price := 80.0
vat := 0.2 // 20%

vatAmount := price * vat
gross := price + vatAmount
fmt.Printf("Net %v, vat %v, gross %v\n", price, vatAmount, gross)

which uses binary floating points, not decimal numbers, so I risk rounding error issues instead. Normally, I want to avoid binary floats in monetary calculations.

big.Rat defines a good set of operations; the problem is that the API to use them makes the code confusingly-opaque. It consists of arithmetic operations:

  • Add implements +
  • Mul implements *
  • Quo implements /
  • Sub implements -

plus related:

  • Abs implements |x| for some x
  • Cmp implements the comparison -1 if x < y; 0 if x == y; +1 if x > y
  • Neg implements -x for some x

It does not provide a remainder operation, needed in some arithmetic contexts.

See also https://npf.io/2014/10/go-nitpicks/

comments powered by Disqus