Reputation: 71400
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:
outer
from the inner
; this got close to working (complained about needing UndecidableInstances
), but doesn't seem quite right; ideally I'd really want to be able to have a way of pushing the same inner
into two different outer
souter
=> Outer inner
), I also fell over because the outer
I'm using in an instance has more type variables (one of them phantom) than the inner
, meaning I wasn't able to legally instantiate the associated type in an instance declarationmerge
in into
with ScopedTypeVariables
to tie it to the type signature for into
; but since the type of merge
doesn't refer to outer
it doesn't helpIs 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
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