aaronlevin
aaronlevin

Reputation: 1443

Cannot Deduce (simple) Typeclass from context

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

Answers (1)

Tikhon Jelvis
Tikhon Jelvis

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

Related Questions