使用复杂类型参数强制记录时出现问题

Problem coercing a record with complex type parameters

我有这个记录:

import Data.Functor.Identity
import Control.Monad.Trans.Identity
import Data.Coerce

data Env m = Env {
        logger :: String -> m ()
    }

env :: Env IO
env = undefined

和这个强制转换函数

decorate 
    :: Coercible (r_ m) (r_ (IdentityT m))   
    => r_ m -> r_ (IdentityT m)        
decorate = coerce

适用于没有问题的记录值:

decoratedEnv :: Env (IdentityT IO)
decoratedEnv = decorate env

但是,如果我定义唯一稍微复杂一点的记录

data Env' h m = Env' {
        logger' :: h (String -> m ())
    }

env' :: Env' Identity IO
env' = undefined

并尝试像我之前那样插入一个 IdentityT 包装器

decoratedEnv' :: Env' Identity (IdentityT IO)
decoratedEnv' = decorate env'

我收到错误:

* Couldn't match type `IO' with `IdentityT IO'
    arising from a use of `decorate'

在我看来,Env' 使用的额外 Identity 参数不应该阻止 coerce 工作。为什么 coerce 在这种情况下会失败?有没有办法让 coerce 工作?

强制 Coercible A B 并不意味着对所有 h.Coercion (h A) (h B) 强制 Coercion (h A) (h B)

考虑这个 GADT:

data T a where
  K1 :: Int  -> T A
  K2 :: Bool -> T B

T A 强制转换为 T B 等同于将 Int 转换为 Bool,这显然不应该发生。

只有当我们知道 h 的参数具有正确的 role(例如 representationalphantom,而不是 nominal).

现在,在您的特定情况下,h 是已知的 (Identity),并且肯定具有正确的 角色 ,因此它应该可以工作。我猜 GHC 强制系统没有那么强大,无法处理这样的“乖”h 而拒绝不乖的,所以它保守地拒绝一切。


添加一个类型孔似乎证实了这一点。我试过了

decoratedEnv' :: Env' Identity (IdentityT IO)
decoratedEnv' = _ decorate' @(Env' Identity)

出现错误

* Couldn't match representation of type: r_0 m0
                           with that of: r_0 (IdentityT m0)
    arising from a use of decorate'
  NB: We cannot know what roles the parameters to `r_0' have;
    we must assume that the role is nominal
* In the first argument of `_', namely decorate'
  In the expression: _ decorate' @(Env' Identity)
  In an equation for decoratedEnv':
      decoratedEnv' = _ decorate' @(Env' Identity)

“注意:”部分就在现场。

我也努力坚持,强行发挥作用

type role Env' representational representational
data Env' h m = Env' {
        logger' :: h (String -> m ())
    }

无济于事:

* Role mismatch on variable m:
    Annotation says representational but role nominal is required
* while checking a role annotation for Env'

我能找到的最佳解决方法是 unwrap/rewrap,并利用 QuantifiedConstraints 本质上要求 h 具有非 nominal 角色:

decorate' 
    :: (forall a b. Coercible a b => Coercible (h a) (h' b))
    => Coercible m m'
    => Env' h m -> Env' h' m'
decorate' (Env' x) = Env' (coerce x)

decoratedEnv' :: Env' Identity (IdentityT IO)
decoratedEnv' = decorate' env'

这不是一个理想的解决方案,因为它违背了 Coercible 的精神。在这种情况下,重新包装只有 O(1) 的成本,但是如果我们有一个 Env' Identity IO 的列表要转换,我们将支付 O(N).