具有逆变位置变量的存在包装实例

Instance for existential wrapper with a variable in contravariant position

我的定义如下:

{-# LANGUAGE ExistentialQuantification #-}

module Test
where

class Named a where
  name :: a -> String

data Wrap = forall a . (Named a, Read a) => Wrap (a -> IO ())

我想为 Wrap 编写 Named 实例。下一个不行:

instance Named Wrap where
  name (Wrap named) =
    let a = undefined
        _ = named a
    in name a

错误:

Could not deduce (Named a0) arising from a use of ‘name’
from the context (Named a, Read a)

但以下有效:

instance Named Wrap where
  name (Wrap named) =
    let a = read undefined
        _ = named a
    in name a

我看到的唯一区别是 aread 中处于协变位置,但在 name 中处于逆变位置。为什么一审声明不起作用?

第一个实例不起作用,因为它不会触发单态限制,所以 a 得到一个多态类型,它在 [=13] 中以 不同方式 实例化=] 和 name a。另一方面,当写 a = read undefined 时,我们发现 a 有一个 typeclass-restricted 类型,所以单态限制开始了,我们必须为 a 选择一个特定的类型;由于 named a 唯一标识此类型,因此选择该类型并且不会在 name a.

中以不同类型实例化

您可以通过打开 NoMonomorphismRestriction 来验证这是正确的解释,从而导致 read 版本失败。

您可以使用 lambda 而不是 let 来解决这个问题,例如:

instance Named Wrap where
    name (Wrap named) = (\a -> (\_ -> name a) (named a)) undefined

(一般来说,let x = e in e'(\x -> e') e是一样的,前提是x是单态的而不是递归的,我在这里重写了两次。)

但是,如果可能的话,我建议对您的方法进行更认真的修改,以完全避免 undefined。标准技巧是:

class Named a where
    name :: proxy a -> String

proxyForFun :: (a -> IO ()) -> Proxy a
proxyForFun _ = Proxy

instance Named Wrap where
    name (Wrap named) = name (proxyForFun named)

但是,name 的类型非常受限:不再可能编写 name 检查其参数的实例,因此如果这是您需要的功能,则此方法不会工作。