Here's some ghci
> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
> :t allEqual
allEqual :: Eq a => [a] -> [a] -> Bool
> :t allEqual <*> reverse
allEqual <*> reverse :: Eq a => [a] -> Bool
> :t reverse
reverse :: [a] -> [a]
I'm trying to work out what exactly is the instance of the applicative in allEqual <*> reverse
.
I tried to reason that
f (a -> b) :: [a] -> [a] -> Bool
f a :: [a] -> [a]
f b :: [a] -> Bool
but this does not make sense to be since f b
is related to a
.
Is their anyway to get ghci to tell me what applicative f
is actually being used here. If not how do haskellers reason about this stuff.
You can use -ddump-ds
to get GHC to show you what instance it's using, like this:
GHCi, version 9.8.1: https://www.haskell.org/ghc/ :? for help
ghci> :{
ghci| allEqual :: Eq a => [a] -> [a] -> Bool
ghci| allEqual = undefined -- or your actual definition
ghci| :}
ghci> :set -ddump-ds -dsuppress-all -dno-suppress-type-applications
ghci> x = allEqual <*> reverse
==================== Desugared ====================
letrec {
x_aR1
= \ @a_aSP $dEq_aT1 ->
let { $dEq_aSQ = $dEq_aT1 } in
let { $dApplicative_aSL = $fApplicativeFUN @[a_aSP] } in
letrec {
x_aSW
= <*>
@((->) [a_aSP])
$dApplicative_aSL
@[a_aSP]
@Bool
(allEqual @a_aSP $dEq_aSQ)
(reverse @a_aSP); } in
x_aSW; } in
returnIO
@[Any]
(: @Any
(unsafeCoerce#
@LiftedRep
@LiftedRep
@(forall {a}. Eq a => [a] -> Bool)
@Any
x_aR1)
([] @Any))
ghci>
The @((->) [a_aSP])
after the <*>
in the output means that it's using the instance for functions (listed as the Applicative ((->) r)
instance in the documentation).
Alternatively, you could figure it out without GHC's help just by looking at the types, like this:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
allEqual :: Eq a => [a] -> [a] -> Bool
reverse :: [a] -> [a]
For simplicity's sake, I'll alpha-convert the latter two types to not reuse the a
type variable:
allEqual :: Eq t => [t] -> [t] -> Bool
reverse :: [t] -> [t]
Remember that ->
is right-associative, and it can also be written in prefix form, so [t] -> [t] -> Bool
is the same as (->) [t] ([t] -> Bool)
. Since allEqual
is the first parameter to <*>
, its type must unify with f (a -> b)
. Similarly, as the second parameter, the type of reverse
must unify with f a
. When you plug all of that in, you get that f
must be (->) [t]
, a
must be [t]
, and b
must be Bool
. A quick sanity check that this is correct is to observe that f b
is then [t] -> Bool
, which after alpha-equivalence is the same type GHC inferred as the output of allEqual <*> reverse
. Since f
is what the Applicative
instance is needed on, it must be using the instance for (->) [t]
(functions that take a list as an argument). Doing :instances ((->) [t])
will show you instance Applicative ((->) [t]) -- Defined in ‘GHC.Base’
, but the instance that actually gets used is more general, one for any kind of function. Once you found the function arrow, doing :info (->)
would show you that: instance Applicative ((->) r) -- Defined in ‘GHC.Base’
.