I’ve read that Go doesn’t actually have true type inference in the sense that functional languages such as ML or Haskell have, but I haven’t been able to find a simple to understand comparison of the two versions. Could someone explain in basic terms how type inference in Go differs from type inference in Haskell, and the pros/cons of each?
See this StackOverflow answer regarding Go’s type inference. I’m not familiar with Go myself but based on this answer it seems like a one-way “type deduction” (to borrow some C++ teminology). It means that if you have:
x := y + z
then the type of x
is deduced by figuring out the type of y + z
, which is a relatively trivial thing to do for the compiler. To do this, the types of y
and z
need to be known a priori: this could be done via type annotations or inferred from the literals assigned to them.
In contrast, most functional languages have type inference that uses all possible information within a module (or function, if the inference algorithm is local) to derive the type of the variables. Complicated inference algorithms (such as Hindley-Milner) often involve some form of type unification (a bit like solving equations) behind the scenes. For example, in Haskell, if you write:
let x = y + z
then Haskell can infer the type not just x
but also y
and z
simply based on the fact that you’re performing addition on them. In this case:
x :: Num a => a
y :: Num a => a
z :: Num a => a
(The lowercase a
here denotes a polymorphic type, often called “generics” in other languages like C++. The Num a =>
part is a constraint to indicate that the type a
support have some notion of addition.)
Here’s a more interesting example: the fixed-point combinator that allows any recursive function to be defined:
let fix f = f (fix f)
Notice that nowhere have we specified the type of f
, nor did we specify the type of fix
, yet the Haskell compiler can automatically figure out that:
f :: t -> t
fix :: (t -> t) -> t
This says that:
- The parameter
f
must be a function from some arbitrary typet
to the same typet
. fix
is a function that receives a parameter of typet -> t
and returns a result of typet
.
1
Type inference in Go is extremely limited and extremely simple. It works only in one language construct (variable declaration) and it simply takes the type of the right-hand side and uses it as the type for the variable on the left-hand side.
Type inference in Haskell can be used everywhere, it can be used to infer the types for the whole program. It is based on unification, which means that (conceptually) all types are inferred “at once” and they can all influence each other: in Go, type information can only flow from the right-hand side of a variable declaration to the left-hand side, never in the other direction and never outside of a variable declaration; in Haskell, type information flows freely in all directions through the entire program.
However, Haskell’s type system is so powerful that type inference can actually fail to infer a type (or more precisely: restrictions have to be put in place so that a type can always be inferred). Go’s type system is so simple (no subtyping, no parametric polymorphism) and its inference so limited that it always succeeds.
2