开发者

How can I give the type inference engine some hints?

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:

  1. The client code did not require any additional type signatures.
  2. Container, which is a polymorphic value (of type Container a), was correctly monomorphed to Container Element.
  3. No newtype wrappers were required in the client code.
  4. It is probably not possible to declare another instance of Foo whose outermost type constructor is Container. 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).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜