In this answer, I will just expand a bit on one of the suggestions I made in a comment.
Is it possible to see the type of f <$>
when acting on tuples?
(<$>)
is a polymorphic function:
GHCi> :t (<$>)
(<$>) :: Functor f => (a -> b) -> f a -> f b
With GHC 8, you can use the TypeApplications
extension to specialise polymorphic functions by supplying instantiations of some or all of their type variables (in this case, f
, a
and b
, in that order):
GHCi> :set -XTypeApplications
GHCi> :t (<$>) @Maybe
(<$>) @Maybe :: (a -> b) -> Maybe a -> Maybe b
GHCi> :t (<$>) @Maybe @Int
(<$>) @Maybe @Int :: (Int -> b) -> Maybe Int -> Maybe b
GHCi> :t (<$>) @Maybe @_ @Bool
(<$>) @Maybe @_ @Bool :: (t -> Bool) -> Maybe t -> Maybe Bool
GHCi> :t (<$>) @_ @Int @Bool
(<$>) @_ @Int @Bool
:: Functor t => (Int -> Bool) -> t Int -> t Bool
GHCi> :t (<$>) @Maybe @Int @Bool
(<$>) @Maybe @Int @Bool :: (Int -> Bool) -> Maybe Int -> Maybe Bool
To use that with pairs, use the prefix syntax for the pair type constructor:
GHCi> :t (<$>) @((,) _)
(<$>) @((,) _) :: (a -> b) -> (t, a) -> (t, b)
GHCi> -- You can use the specialised function normally.
GHCi> -- That includes passing arguments to it.
GHCi> f x = x + 1
GHCi> :t (<$>) @((,) _) f
(<$>) @((,) _) f :: Num b => (t, b) -> (t, b)
The _
in ((,) _)
leaves it unspecified what the type of the first element of the pair (which is the first argument of the (,)
type constructor) should be. Every choice of it gives rise to a different Functor
. You can be more specific if you wish:
GHCi> :t (<$>) @((,) String) f
(<$>) @((,) String) f :: Num b => (String, b) -> (String, b)
Lastly, it is worth having a look at what happens if you try that with 3-tuples:
GHCi> :t (<$>) @((,,) _ _) f
(<$>) @((,,) _ _) f
:: (Num b, Functor ((,,) t t1)) => (t, t1, b) -> (t, t1, b)
As Daniel Wagner discusses in his answer, base doesn't define a Functor
instance for 3-tuples. In spite of that, the type checker cannot exclude the possibility that someone somewhere might have defined an instance specific for some choice of the first two type parameters, however pointless that would be. For that reason, the speculative constraint Functor ((,,) t t1)
shows up in the type (no such thing happens with pairs because there is a Functor ((,) a)
instance in base). As expected, that blows up as soon as we try to instantiate the first two type parameters:
GHCi> :t (<$>) @((,,) Bool String) f
<interactive>:1:1: error:
• Could not deduce (Functor ((,,) Bool String))
arising from a use of ‘<$>’
from the context: Num b
bound by the inferred type of
it :: Num b => (Bool, String, b) -> (Bool, String, b)
at <interactive>:1:1
• In the expression: (<$>) @((,,) Bool String) f