Can't infer (simple) Typeclass out of context
I am getting an error on a fairly simple example and I cannot figure out what is wrong. What I am doing is very similar to mempty
in Monoid
, here is a simple version (I include my quantified type in case something is related to the problem):
data Field a = Field String
| forall b. EsMappable b => ReferenceField String (a -> b)
class EsMappable a where
fields :: [Field a]
toMapping :: (EsMappable a) => a -> String
toMapping a = go "" fields
where go str [] = str
go str (ReferenceField name _ : xs) = go (name ++ str) xs
go str (Field name : xs) =go (name ++ str) xs
The error I am getting:
Could not deduce (EsMappable t0) arising from a use of βfieldsβ
from the context (EsMappable a)
bound by the type signature for
toMapping :: EsMappable a => a -> String
at search2.hs:11:14-42
The type variable βt0β is ambiguous
In the second argument of βgoβ, namely βfieldsβ
In the expression: go "" fields
In an equation for βtoMappingβ:
toMapping a
= go "" fields
where
go str [] = str
go str (ReferenceField name _ : xs) = go (name ++ str) xs
go str (Field name : xs) = go (name ++ str) xs
Note : if I change the class EsMapapble
to: fields :: a -> [Field a]
and then toMapping
change to go "" (fields a)
, it works.
My question is , why am I getting this error? This is not the same as mempty
? What prevents the GHC from resolving correctly fields
?
Thank!
source to share
The problem is that the compiler has no way to bind the usage fields
to your argument a
. Your function go
can accept [Field a]
for anyone a
, so you need to somehow specifically constrain it to the same as a
in your argument type.
You can do it with ScopedTypeVariables
:
toMapping :: forall a. (EsMappable a) => a -> String
toMapping _ = go "" (fields :: [Field a])
You will need an additional forall a
one to explicitly use the type variable a
. This limitation is ScopedTypeVariables
in the interest of backward compatibility.
It also wouldn't be a problem if you used your argument a
with values Field a
in go
. As a contrived example, the following typechecks are not explicitly signed:
go str (ReferenceField name f : xs) = go (name ++ str) xs `const` f a
This forces you f
to accept a type argument a
that holds the entire list for that particular one a
. So you could use this trick to avoid expanding ScopedTypeVariables
if you really wanted to! I wouldn't say that though: the extension is about as harmless as they are, and makes the code clearer. This example was just to illustrate my point.
source to share