Let's look at the snd type:
snd :: (foo, x) -> x
(I renamed type variables for clarity)
What is a type state for a tuple with types foo and x , return something of type x . Something important to know here is that while the value system is aka. Haskell's runtime is lazy, a system like Haskell is strict, which means that both foo and x must be known before snd can be called.
In the first case, when you have only Num b => (b -> b, String) , calling snd will leave b ambiguous, because you will not specify its specific type anywhere, and it cannot be deduced from the returned type because that foo ~ b , which is different from x . In other words: since (b, b) can be a tuple of any type of number, and the type checker cannot determine which one is ambiguous. The trick here is that we will introduce default Haskell rules that state that if a numeric type is ambiguous, it should default to Integer . If you enabled warnings with -Wall , he would say that this is happening. So our type becomes (Integer -> Integer, String) and snd can be called.
However, in the second case, we still manage to draw b using the default rules, but by default there is no Arrow for a , so weβre stuck! You must explicitly indicate which arrow you want to continue! You can do this by first using the aTuple10 value somewhere else:
let bla = aTuple10 -- We do this because `aTuple10` can have type variables, but `bla` cannot (by default) fst bla (23 :: Int) -- This fixes the type of `bla`, so that `a ~ (->)` and `b ~ Int` print $ snd bla -- So the arrow isn't ambiguous here
... or you can just specify the type you want:
print $ snd (aTuple10 :: (Int -> Int, String))
PS , if you want to change the type of ambiguous numbers by default, the default keyword can help you out.