GHC类型函数和作用域类型变量

4
考虑以下情况,这是在ScopedTypeVariables上下文中编写的:
defaultGetRestView :: forall view master .
                   ( PersistEntity (PersistentView master view)
                   , PersistStore (YesodPersistBackend master (HandlerT master IO))
                   , MonadTrans (YesodPersistBackend master), YesodPersist master
                   , PersistMonadBackend (YesodPersistBackend master (HandlerT master IO))
                     ~ PersistEntityBackend (PersistentView master view)
                   ) => Key (PersistentView master view) -> HandlerT master IO view
defaultGetRestView key = ((runDB $ get404 key) :: HandlerT master IO (PersistentView master view)) >>= (return . (view :: PersistentView master view -> view))
我遇到了一个我不理解的类型错误。我明确声明我调用的“view”函数是应用于(PersistentView master view)的函数:
 NB: `PersistentView' is a type function, and may not be injective
  The type variable `master0' is ambiguous
  Possible fix: add a type signature that fixes these type variable(s)
  Expected type: PersistentView master view -> view
    Actual type: PersistentView master0 view -> view
  In the second argument of `(.)', namely
    `(view :: PersistentView master view -> view)'

请尝试将此代码缩减为SSCCE - leftaroundabout
1个回答

4
"可能不是单射"是错误的关键部分。也就是说,我们可能会有相等性。
PersistentView master view ~ PersistentView master0 view

这里需要解释一下 mastermaster0 之间的区别。由于非注入性,写成 view :: PersistentView master view -> view 固定了返回类型中使用的类型变量 (view),但它并没有固定 master 类型变量,因此仍然存在歧义。

对于 view 函数的类型来说,它看起来本质上是模棱两可的,因为它没有任何方法可以固定 master 类型变量的值。通常情况下,会使用额外的虚拟参数来解决这个问题,例如:

view :: master -> PersistentView master view -> view
view _unused persistentView = ...

然后,view(undefined::master) pv 可以让用户选择所要评估的 view 类型。


但是为什么有限范围的类型变量不足以修复它们呢?特别是,(runDB $ get404 key) 行被设置为将 (PersistentValue master view) 传递给 'view' 函数,该函数期望一个 (PersistentValue master view)。而且这两个 master 变量都是“相同类型”。我不明白为什么 GHC 要发明一个 master0。 - nomen
因为类型签名固定了 PersistentView master view -> view 的值。由于 (->) 是可逆的(它是一个类型构造器,而不是类型族),这固定了类型 view 和类型 PersistentValue master view。然而,这还不足以推断出 master 应该是什么。我会在我的答案中提供建议。 - chi
谢谢。我也发现设置一个FunctionalDependency视图->主视图解决了问题,一旦我明白了为什么需要代理/虚拟。 (只是作为一个实验,供我们参考,我更喜欢代理) - nomen
由于您正在使用类型族,我建议您继续使用它们而不是FunDeps。也就是说,我会添加另一个“Master view”类型族,并从“PersistentView”类型族中删除“master”参数。当然,这是个人偏好的问题--我倾向于使用FunDeps或TypeFamilies,但不会同时使用两者。 - chi
现在重构实际上在这里是不可能的 - 我需要类型类定义中的主类和视图,因此其范围内定义的所有类型族都需要两个参数。我已经有了数据 Proxy a = Proxy 在作用域中,所以我将把 (undefined :: master) 改为 (Proxy :: Proxy master)。 - nomen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接