We use type annotations to avoid ambiguity. Type applications can be used for the same purpose. For example
x :: Num a => a
x = 5
main :: IO ()
main = print x
This code has an ambiguity error. We know that a
has a Num
instance, and in order to print it we know it needs a Show
instance. This could work if a
was, for example, an Int
, so to fix the error we can add a type annotation
main = print (x :: Int)
Another solution using type applications would look like this
main = print @Int x
To understand what this means we need to look at the type signature of print
.
print :: Show a => a -> IO ()
The function takes one parameter of type a
, but another way to look at it is that it actually takes two parameters. The first one is a type parameter, the second one is a value whose type is the first parameter.
The main difference between value parameters and the type parameters is that the latter ones are implicitly provided to functions when we call them. Who provides them? The type inference algorithm! What TypeApplications
let us do is give those type parameters explicitly. This is especially useful when the type inference can't determine the correct type.
So to break down the above example
print :: Show a => a -> IO ()
print @Int :: Int -> IO ()
print @Int x :: IO ()