Reputation: 474
I'd like to provide a generic implementation of a multi-parameter typeclass, then provide overlapping instances for specific instantiations of said typeclass. This works with a single-parameter class, but fails to compile once I add type parameters. Below is a toy example of the kind of instances I'm trying to write:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
class Foo a b where
foo :: a -> b -> String
instance {-# OVERLAPPABLE #-} Foo a b where
foo _ _ = "foo"
instance Foo Int String where
foo _ y = y
mapFoo :: a -> [b] -> [String]
mapFoo x ys = map (foo x) ys
This example fails to compile due to an error at the call site:
• Overlapping instances for Foo a b arising from a use of ‘foo’
Matching instances:
instance [overlappable] Foo a b -- Defined at overlapping.hs:7:31
instance Foo Int String -- Defined at overlapping.hs:10:10
(The choice depends on the instantiation of ‘a, b’
To pick the first instance above, use IncoherentInstances
when compiling the other instance declarations)
• In the first argument of ‘map’, namely ‘(foo x)’
In the expression: map (foo x) ys
In an equation for ‘mapFoo’: mapFoo x ys = map (foo x) ys
|
14 | mapFoo x ys = map (foo x) ys
I've tried adding various pragma annotations, as well as explicit type applications but haven't been able to get this to compile. Is there a way to do so? Or is there a better way to express the same kind of behavior?
Upvotes: 0
Views: 94
Reputation: 80744
It's not the instances themselves, it's the function. In order to use foo
, mapFoo
must have a constraint, which would require the caller to provide an instance of the class:
mapFoo :: Foo a b => a -> [b] -> [String]
If you meant to express that mapFoo
should always use the "base" instance because a
and b
are unknown here, then just use it directly:
mapFoo x ys = map (const "foo") ys
And if you meant for the compiler to magically choose an instance depending on how a
and b
get instantiated later, then this cannot happen. At the time mapFoo
is compiled, it's not yet known what a
and b
are, and therefore which implementation of foo
to use. It must be able to either choose an implementation locally based on locally-known types or get it as a parameter from the caller.
Upvotes: 3