Search code examples
haskell

Can ghci tell us what instance of a class is being used


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.


Solution

  • 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’.