How do you know what `a0` in an error message specifically refers to

It is common to come across error messages that have a0 in them (or similar type variables p1, t0, etc.) and I am always wondering what specifically they refer to. Here is an example.

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE InstanceSigs #-}

module Expr1 where

import TextShow ( showbParen, Builder, TextShow(showb, showbPrec) )

data Expr a = Lit a
            | Add (Expr a) (Expr a)
            | Mult (Expr a) (Expr a)
  deriving (Show, Read)

instance TextShow a => TextShow (Expr a) where
  showbPrec :: Int -> Expr a -> Builder
  showbPrec p e =
    case e of
      Lit a -> showb a
      Add e1 e2 -> showbHelper p 5 "+" e1 e2
      Mult e1 e2 -> showbHelper p 6 "*" e1 e2

showbHelper :: (TextShow a)  => Int -> Int -> Builder -> Expr a -> Expr a -> Builder
showbHelper outerPrec thisPrec op e1 e2 =
  showbParen (outerPrec > thisPrec)
  $ showbPrec thisPrec e1 <> op <> showbPrec thisPrec e2
plus :: Builder
plus = "+"

expr1 :: Expr Integer
expr1 = Add (Lit 2) (Lit 3)

expr2 :: Expr Integer
expr2 = Mult (Lit 4) (Lit 5)

bb1 = showbParen (True) $  showbPrec 6 expr1 
                        <> plus 
                        <> showbPrec 5 expr2

All is good here but I wanted to substitute in the value for expr1 and expr2 and tried this first:

bb1 = showbParen (True) 
        $  showbPrec 6 (Add (Lit 2) (Lit 3))
        <> plus 
        <> showbPrec 5 expr2

And got the below error with the entire line showbPrec 6 (Add (Lit 2) (Lit 3)) underlined. (I’m using VSCode with HLS)

• Ambiguous type variable ‘a0’ arising from a use of ‘showbPrec’  
prevents the constraint ‘(TextShow a0)’ from being solved.  
Probable fix: use a type annotation to specify what ‘a0’ should be.  
These potential instances exist:  
instance (TextShow a, TextShow b) => TextShow (Either a b)  
-- Defined in ‘TextShow.Data.Either’  
instance TextShow Ordering -- Defined in ‘TextShow.Data.Ord’  
instance TextShow a => TextShow (Expr a)  
-- Defined at /home/klequis/d/learn/haskell/book/haskell-in-depth/mini-proj/expr/app/Expr1.hs:13:10  
...plus 26 others  
...plus 208 instances involving out-of-scope types  
(use -fprint-potential-instances to see them all)  
• In the first argument of ‘(<>)’, namely  
‘showbPrec 6 (Add (Lit 2) (Lit 3))’  
In the second argument of ‘($)’, namely  
‘showbPrec 6 (Add (Lit 2) (Lit 3)) <> plus <> showbPrec 5 expr2’  
In the expression:  
showbParen (True)  
$ showbPrec 6 (Add (Lit 2) (Lit 3)) <> plus <> showbPrec 5 expr2typecheck(-Wdeferred-type-errors)

It wasn’t immediately clear to me what a0 was.

Only figuring out the fix is adding the type for Add (Lit 2) (Lit 3) – [A] …

bb1 = showbParen (True) 
        $  showbPrec 6 (Add (Lit 2) (Lit 3) :: Expr Int) -- [A]
        <> plus 
        <> showbPrec 5 expr2

… did I see that a0 is probably the second argument to showbPrec (is that correct?)

:t showbPrec
showbPrec :: TextShow a => Int -> a -> Builder

Is that the process for figuring out the error message or am I missing something that makes it more straight forward to know what a0 is?

Edit: Perhaps one good answer to that would be how you approach or think through error messages.

The error message could definitely be better. I think we’re already tracking a similar issue over at the error-messages issue tracker, but progress is slow.

The reason it reports the showbPrec function and not the Add ... part directly is that the type variable is bound there:

showbPrec :: forall a. TextShow a => Int -> a -> Builder
--           ^^^^^^^^^ binding site of the type variable 'a'

In standard Haskell (2010) you are not allowed to write that forall a. binder and there is no direct way to say what type it should be at a particular call site. With the ExplicitForall GHC extension you can write the forall a. binder and with the TypeApplications extension you can manually pass the type that should be used. Luckily the new GHC2021 standard extension set includes both of those extensions, so they are practically part of the standard language at this point.

You can use TypeApplications to disambiguate the type variable like this:

bb1 = showbParen (True) 
        $  showbPrec @Int 6 (Add (Lit 2) (Lit 3))
        <> plus 
        <> showbPrec 5 expr2

That is the most direct fix and GHC could also pretty easily suggest that.

1 Like

Reporting the provenance of unification variables would not be difficult (a bit fiddly maybe), and would materially improve error messages. We have a ticket: Provide the provenance of unification variables in error messages when possible (#15678) · Issues · Glasgow Haskell Compiler / GHC · GitLab. Just needs someone to execute on it!

Simon

6 Likes