The Hindley-Milner type inference is used for Hindley-Milner type systems, a restriction of System-F type systems. The interesting feature of HM type systems is that they have parametric polymorphism (aka. generics). That is the single biggest type system feature that Golang refuses to have.
With that frustrating restriction, HM-style type inference is impossible. Let's look at the untyped code:
func f(a) {
return a.method()
}
What is the type of f
? We might notice that a
must have a method, so we could use an anonymous interface: func f(a interface { method() ??? }) ???
. However, we have no idea what the return type is. With type variables, we could declare the type as
func f[T](a interface{ method() T }) T
However, Go does not have type variables so this won't work. While implicit interfaces make some aspects of type inference easier, we now have no way to find out the return type of a function call. The HM-system requires all functions to be declared rather than be implied, and each name can only have a single type (whereas Go's methods can have different types in different interfaces).
Instead, Go requires that functions are always fully declared, but allows variables to use type inference. This is possible because the right hand side of an assignment variable := expression
already has a known type at that point of the program. This kind of type inference is simple, correct, and linear.
- The type of a variable is immediately known at the point of the declaration, whereas HM inference has to potentially type-check the whole program first. This has a noticeable impact on the quality of error messages, too.
- Go's type inference approach will always select the most specific type for a variable, in contrast to HM which picks the most general type. This cleanly works with subtyping, even with Go's implicit interfaces.