In (very) modern GHC's, I can write this:
{-# LANGUAGE TypeFamilies #-}
-- consider this part "library" code, changeable at will
data Container a = Container
data Element
class Foo a where foo :: a -> Int
instance Element ~ a => Foo (Container a) where foo _ = 0
-- consider this part "client" code; bonus points if it can remain exactly as is
main = print (foo Container)
Salient points:
- The client code did not require any additional type signatures.
Container
, which is a polymorphic value (of typeContainer a
), was correctly monomorphed toContainer Element
.- No newtype wrappers were required in the client code.
- It is probably not possible to declare another instance of
Foo
whose outermost type constructor isContainer
. This is not a property I care about preserving in the following discussion.
Can this be done in a more backwards-compatible way? My first attempt looked like this:
{-# LANGUAGE FlexibleInstances #-}
data Container a = Container
data Element
class Foo a where foo :: a -> Int
instance Foo (Container Element) where foo _ = 0
main = print (foo Container)
...which gives an error:
test.hs:8:15:
No instance for (Foo (Container a0))
arising from a use of `foo'
Possible fix: add an instance declaration for (Foo (Container a0))
In the first argument of `print', namely `(foo Container)'
In the expression: print (foo Container)
In an equation for `main': main = print (foo Container)
I realized that this error probably arises because the instance doesn't use a type variable as the argument to Container
, so I also tried:
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-}
data Container a = Container
data Element
class Foo a where foo :: a -> Int
instance Convertible a Element => Foo (Container a) where foo _ = 0
class Convertible a b where
-- convert is not necessary in this tiny example, but it would
-- be necessary in my not-so-tiny use case
convert :: Container a -> Container b
instance Convertible a a where
convert = id
main = print (foo Container)
But this just pushes the problem to the Convertible
type class:
test.hs:14:19:
No instance for (Convertible a0 Element)
arising from a use of `foo'
Possible fix:
add an instance declaration for (Convertible a0 Element)
In the first argument of `print', namely `(foo Container)'
In the expression: print (foo Container)
In an equation for `main': main = print (foo Container)
A similar error arises from writing an instance for Convertible Element Element
instead of Convertible a a
. My last-ditch attempt was to specialize even further:
data Container a = Container
data Element
class Foo a where foo :: a -> Int
instance IsElement a => Foo (Container a) where foo _ = 0
class IsElement a where
convert :: a -> Element
instance IsElement Element where
convert = id
main = print (foo Container)
...which has the notable benefit of being H98, but the notable downside of still not qu开发者_运维百科ite working:
test.hs:10:19:
Ambiguous type variable `a0' in the constraint:
(IsElement a0) arising from a use of `foo'
Probable fix: add a type signature that fixes these type variable(s)
In the first argument of `print', namely `(foo Container)'
In the expression: print (foo Container)
In an equation for `main': main = print (foo Container)
So, the question is this: is there some similar implementation that achieves properties 1, 2, and 3 above, yet does not require the type equality operator?
edit I thought rank-2 types might help:
{-# LANGUAGE FlexibleInstances, RankNTypes #-}
data Container a = Container
data Element
class Foo a where foo :: a -> Int
instance Foo (forall a. Container a) where foo _ = 0
main = print (foo Container)
...but alas, still no dice:
test.hs:6:14:
Illegal polymorphic or qualified type: forall a. Container a
In the instance declaration for `Foo (forall a. Container a)'
I don't understand what you're trying to do here. In particular, why do you care that Container
is polymorphic when you only want the client to work with Container Element
?
That said, how about using a different kind for the type class?
data Container a = Container
data Element
class Foo a where foo :: a x -> Int
instance Foo Container where foo _ = 0
Now it truly doesn't matter which type Container is instantiated to.
If this doesn't work, I suspect the answer is "no, you can't do precisely what you want." Although you could export container = Container :: Container Element
and have client code use that instead of Container
.
Edit: given the full context, and that rank-2 types aren't possible either, I am reasonably certain that there isn't a solution given the problem constraints. The best workaround I can think of is creating a new function xcast :: XConfig a -> XConfig Layout
. This would allow you to write instance Binding (XConfig a -> Foo)
.
精彩评论