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

Yur*_*ras 2 haskell existential-type typeclass

我有以下定义:

{-# LANGUAGE ExistentialQuantification #-}

module Test
where

class Named a where
  name :: a -> String

data Wrap = forall a . (Named a, Read a) => Wrap (a -> IO ())
Run Code Online (Sandbox Code Playgroud)

我想写一个Named实例Wrap.下一个不起作用:

instance Named Wrap where
  name (Wrap named) =
    let a = undefined
        _ = named a
    in name a
Run Code Online (Sandbox Code Playgroud)

错误:

Could not deduce (Named a0) arising from a use of ‘name’
from the context (Named a, Read a)
Run Code Online (Sandbox Code Playgroud)

但以下工作:

instance Named Wrap where
  name (Wrap named) =
    let a = read undefined
        _ = named a
    in name a
Run Code Online (Sandbox Code Playgroud)

我看到的唯一区别a是在协变位置read,但在逆变的位置name.为什么第一个实例声明不起作用?

Dan*_*ner 6

第一个实例不起作用,因为它不会触发单态限制,因此a获取一个多态类型,在和中实例化的方式不同.另一方面,在写入时,我们发现它具有类型类限制类型,因此单态限制启动,我们必须选择特定类型; 由于唯一地标识了这种类型,因此选择了该类型,并且不在其他类型中实例化.named aname aa = read undefinedaanamed aname a

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

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

instance Named Wrap where
    name (Wrap named) = (\a -> (\_ -> name a) (named a)) undefined
Run Code Online (Sandbox Code Playgroud)

(一般来说,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)
Run Code Online (Sandbox Code Playgroud)

但是,类型name是非常严格的:不再可能编写name检查其参数的实例,因此如果这是您需要的功能,则此方法将不起作用.