Reputation: 1443
I'm getting an error in a pretty simple example and I'm unable to figure out what is wrong. What I'm doing is very similar to mempty
in Monoid
, here is a simple version (I include my quantified type in case that has something to do with the issue):
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 get is:
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 EsMapapble
class to: fields :: a -> [Field a]
and then in toMapping
I change go "" (fields a)
, it works.
My Question: why am I getting this error? Is this not the same as mempty
? What is preventing GHC from properly resolving fields
?
Thank you!
Upvotes: 3
Views: 187
Reputation: 68152
The problem is that the compiler has no way to link your use of fields
to your argument a
. Your go
function can take in a [Field a]
for any a
, so you need to somehow specifically constrain it to be the same as the a
in your argument type.
You can do this nicely with ScopedTypeVariables
:
toMapping :: forall a. (EsMappable a) => a -> String
toMapping _ = go "" (fields :: [Field a])
You need the extra forall a
to explicitly make the a
type variable scoped. This is a restriction on ScopedTypeVariables
in the interest of backwards compatibility.
This would also not have been a problem if you had used your a
argument with the Field a
values in go
. As a contrived example, the following typechecks without the explicit signature:
go str (ReferenceField name f : xs) = go (name ++ str) xs `const` f a
This forces f
to take an argument of type a
, which constrains the whole list to that specific a
. So you could actually use this trick to avoid the ScopedTypeVariables
extension if you really wanted to! I wouldn't suggest that though: the extension is about as harmless as they come and makes the code much clearer. This example was just for illustrating my point.
Upvotes: 5