Here's how I think of it.
You have a function coerce isPrefixOf
, and via context that function is constrained to have type String -> String -> Any
. isPrefixOf
on its own has type Eq a => [a] -> [a] -> Bool
.
Clearly coerce
needs to convert the Bool
into an Any
in the return value, but what about the arguments? Are we instantiating isPrefixOf
to [Char] -> [Char] -> Bool
and then coercing to [Char] -> [Char] -> Any
? Or are we instantiating isPrefixOf
to [T] -> [T] -> Bool
(for some T
) and then coercing T
to Char
as well as Bool
to Any
? We need to know the instantiation of isPrefixOf
before we can say whether this is valid.1
If we were applying isPrefixOf
directly2 then the fact that we were processing a String
would instantiate isPrefixOf
's type variable to Char
and everything would work. But you never directly use isPrefixOf
; you use coerce isPrefixOf
. So those strings you're processing are not connected to the a
in isPrefixOf
's type, they're connected to the resulting type of coerce isPrefixOf
. That doesn't help us instantiate the type of isPrefixOf
before coerce
gets hold of it. That a
could be anything that coerce
could translate into Char
, it is not forced to be Char
by this context. Something else is needed to tell us that a
must be Char
.
That ambiguity is what GHC is complaining about. It's not that the compiler isn't clever enough to notice that the only possible choice for isPrefixOf
is [Char] -> [Char] -> Any
, rather the code you've written is actually missing a piece of information the compiler needs to (correctly) deduce that.
coerce
utterly destroys type inference "through" it, because as far as type inference is concerned coerce :: a -> b
(whether the Coercible a b
constraint actually holds up to scrutiny is another matter). There are no constraints on what types coerce
can "try" to convert between, only on what it can successfully convert, so no chains of unification can be drawn through coerce
. You'll need to pin down the types of each side independently, if there are any type variables.3
1 And actually, there could be multiple valid ways to instantiate it that result in different behaviour for your final function. An obvious one would be newtype CaseInsensitiveChar = C Char
where the Eq
instance uses toLower
; isPrefixOf :: [CaseInsensitiveChar] -> [CaseInsensitiveChar] -> Bool
can also be coerced to [Char] -> [Char] -> Any
, but has different behaviour than a coerced isPrefixOf :: [Char] -> [Char] -> Bool
.
2 Or rather, passing it to a foldMap
which is applied to strings.
3 By "side" I'm referring to the way isPrefixOf
is on the "inside" of a coerce
application, and everything else only interacts with the result of coerce
and so is on the "outside".