Ben
Ben

Reputation: 71400

How can I resolve this type class ambiguity?

Here's my minimal example:

{-# LANGUAGE MultiParamTypeClasses, RankNTypes #-}

import Control.Lens

class Into outer inner where
    factory :: inner -> outer
    merge :: inner -> inner -> inner

-- Given an inner item, a lens and an outer item, use factory to construct a new
-- outer around the inner if the Maybe outer is Nothing, or else use merge to combine
-- the argument inner with the one viewed through the lens inside the outer
into :: Into outer inner =>
    inner -> Lens' outer inner -> Maybe outer -> Maybe outer
inner `into` lens = Just . maybe (factory inner) (over lens (merge inner))

This fails to compile with the following error:

GHCi, version 7.6.2: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
[1 of 1] Compiling Main             ( foo.hs, interpreted )

foo.hs:10:62:
    Could not deduce (Into outer0 inner) arising from a use of `merge'
    from the context (Into outer inner)
      bound by the type signature for
                 into :: Into outer inner =>
                         inner -> Lens' outer inner -> Maybe outer -> Maybe outer
      at foo.hs:9:9-84
    The type variable `outer0' is ambiguous
    Possible fix: add a type signature that fixes these type variable(s)
    In the second argument of `over', namely `(merge inner)'
    In the second argument of `maybe', namely
      `(over lens (merge inner))'
    In the second argument of `(.)', namely
      `maybe (factory inner) (over lens (merge inner))'
Failed, modules loaded: none.
Prelude> 

I understand why this error occurs; that call to merge could be using a different instance of Into (with a different outer but same inner) than the one selected by the constraint on the whole into function. But I can't figure out a way to resolve it.

Things I've tried:

Is there any way I can be explicit about using the same type class instance for merge as for the whole of into? Or any other way I can constrain the type system into needing to do that? Ideally I'd like to keep the class so my instance declarations are still this simple:

instance (Hashable v, Eq v) => Into (VarInfo s k v) (HashSet v) where
    -- VarInfo is just a record type with 2 fields, the second being a HashSet v
    factory = VarInfo (return ())
    merge = HashSet.intersection

Upvotes: 2

Views: 456

Answers (1)

kosmikus
kosmikus

Reputation: 19637

Having a class method that does not mention all of the class variables is rarely a good idea (unless these class variables are uniquely determined by a functional dependency).

The solution is to make the class hierarchy more precise. Here, you can create a second class for merge:

class Mergeable a where
    merge :: a -> a -> a

class Mergeable inner => Into outer inner where
    factory :: inner -> outer

You might be able to use the more general Semigroup class rather than the ad-hoc Mergeable class, too, but that depends on the details of your application and the properties of merge.

Upvotes: 6

Related Questions