RussAbbott
RussAbbott

Reputation: 2738

Ambiguous intermediate result

Suppose I have a typeclass, two instances, and a function defined as follows.

class C c where
  newC :: String -> c
  showC :: c -> String

data C1 = C1 String  

instance C C1 where
  newC string = C1 string  
  showC (C1 string) = string

data C2 = C2 String  

instance C C2 where
  newC string = C2 string  
  showC (C2 string) = string


topFn :: String -> String 
topFn = showC . newC

When loaded, GHC produces an "Ambiguous type" error,

Ambiguous type variable ‘c0’ arising from a use of ‘showC’
prevents the constraint ‘(C c0)’ from being solved.
Probable fix: use a type annotation to specify what ‘c0’ should be.
These potential instances exist:
   instance C C1 -- Defined at ambigType.hs:8:10
   instance C C2 -- Defined at ambigType.hs:14:10

I understand that the intermediate value could be either C1 or C2 and that without further information GHC has no way to determine which it should be.

Writing two versions of topFn solves the problem.

topFn1 :: String -> String 
topFn1 = showC . (newC :: String -> C1)

topFn2 :: String -> String 
topFn2 = showC . (newC :: String -> C2)

But since these are essentially identical functions, this seems like an ugly solution. Is there a better way?

Thanks.

Upvotes: 1

Views: 63

Answers (1)

Alec
Alec

Reputation: 32319

Yes, use the TypeApplications, AllowAmbiguousTypes, ExplicitForall, and ScopedTypeVariables extensions. Then, modify topFn to be

topFn :: forall a. C a => String -> String 
topFn = showC . newC @a

Then, you can call topFn using an explicit type-application: topFn @C1 "calling the C1 variant" and topFn @C2 "calling the C2 variant". If you want, you can define:

topFn1 = topFn @C1
topFn2 = topFn @C2

Upvotes: 3

Related Questions