Say you're introducing a class of types that have a size in bytes.
class SizeOf a where
sizeOf :: a -> Int
The problem is that the size should be constant for every value of that type. We don't actually want the sizeOf
function to depend on a
, but only on it's type.
Without type applications, the best solution we had was the Proxy
type defined like this
data Proxy a = Proxy
The purpose of this type is to carry type information, but no value information. Then our class could look like this
class SizeOf a where
sizeOf :: Proxy a -> Int
Now you might be wondering, why not drop the first argument altogether? The type of our function would then just be sizeOf :: Int
or, to be more precise because it is a method of a class, sizeOf :: SizeOf a => Int
or to be even more explicit sizeOf :: forall a. SizeOf a => Int
.
The problem is type inference. If I write sizeOf
somewhere, the inference algorithm only knows that I expect an Int
. It has no idea what type I want to substitute for a
. Because of this, the definition gets rejected by the compiler unless you have the {-# LANGUAGE AllowAmbiguousTypes #-}
extension enabled. In that case the definition compiles,it just can't be used anywhere without an ambiguity error.
Luckily, the introduction of type applications saves the day! Now we can write sizeOf @Int
, explicitly saying that a
is Int
. Type applications allow us to provide a type parameter, even if it doesn't appear in the actual parameters of the function!