强制和角色-无法与GHCi的类型签名一起编译

时间:2018-11-15 08:01:03

标签: haskell ghc

在GHCi中键入以下内容

>:set -XAllowAmbiguousTypes
>import Data.Coerce
>fcm f = coerce . foldMap (coerce . f)
>:t fcm
fcm
  :: (Foldable t, Monoid a1, Coercible a1 c, Coercible a2 a1) =>
     (a3 -> a2) -> t a3 -> c

好的,这就是我的期望。将其复制粘贴到文件中。

{-# LANGUAGE AllowAmbiguousTypes #-}

import Data.Coerce

fcm :: (Foldable t, Monoid a1, Coercible a1 c, Coercible a2 a1) 
    => (a3 -> a2) -> t a3 -> c
fcm f = coerce . foldMap (coerce . f)

现在,如果将其加载到GHCi中,则会出现错误-

Weird.hs:7:9: error:
    • Couldn't match representation of type ‘a0’ with that of ‘c’
    arising from a use of ‘coerce’
      ‘c’ is a rigid type variable bound by
    the type signature for:
      fcm :: forall (t :: * -> *) a1 c a2 a3.
             (Foldable t, Monoid a1, Coercible a1 c, Coercible a2 a1) =>
             (a3 -> a2) -> t a3 -> c
    at Weird.hs:(5,1)-(6,30)
    • In the first argument of ‘(.)’, namely ‘coerce’
      In the expression: coerce . foldMap (coerce . f)
      In an equation for ‘fcm’: fcm f = coerce . foldMap (coerce . f)
    • Relevant bindings include
    fcm :: (a3 -> a2) -> t a3 -> c (bound at Weird.hs:7:1)

嗯? a0来自哪里?如果删除类型签名,代码将再次编译正常。看起来我们现在有了一个函数,其类型无法向编译器解释,GHCi无法告诉我们该类型是什么(它只会给我们带来错误)。 GHC似乎在谈论用户没有面对的内部类型变量(在这种情况下为a0)。

这与环绕在Coercible周围的黑魔法有关吗?来自GHC.Types

Note [Kind-changing of (~) and Coercible]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(~) and Coercible are tricky to define. To the user, they must appear as
constraints, but we cannot define them as such in Haskell. But we also cannot
just define them only in GHC.Prim (like (->)), because we need a real module
for them, e.g. to compile the constructor's info table.

Furthermore the type of MkCoercible cannot be written in Haskell
(no syntax for ~#R).

So we define them as regular data types in GHC.Types, and do magic in TysWiredIn,
inside GHC, to change the kind and type.

我理解正确吗?还是可以为函数编写类型签名并成功编译代码?对于它的价值,我感兴趣的是能够编写的东西稍微简单一些-

fcm :: (Monoid m, Coercible m b, Coercible b m, Foldable f) => (a -> b) -> f a -> b
fcm f = coerce . foldMap (coerce . f)

sumBy = fcm @Sum 

1 个答案:

答案 0 :(得分:2)

下面是一个简单的例子:

> :set -XAllowAmbiguousTypes
> import Data.Coerce
> f = coerce . coerce
> :t f
f :: (Coercible a1 c, Coercible a2 a1) => a2 -> c

到目前为止,太好了。一切看起来都很合理。但是,如果以下代码将无法编译:

f :: (Coercible a1 c, Coercible a2 a1) => a2 -> c
f = coerce . coerce

• Couldn't match representation of type ‘a0’ with that of ‘c’
    arising from a use of ‘coerce’
  ‘c’ is a rigid type variable bound by
    the type signature for:
      f :: forall a1 c a2. (Coercible a1 c, Coercible a2 a1) => a2 -> c

为什么会这样? a0来自哪里?

简而言之,在coerce . coerce中,GHC必须选择一个中间类型,即第一次强制的结果。原则上可以是任何值,因此GHC会为其生成一个新的类型变量:上面为此选择了a0

然后对代码进行类型检查,并将需要约束Coercible a0 c, Coercible a2 a0。签名提供的签名是不同的。在这里,GHC不会尝试“匹配”它们并选择a0 = a1。确实,在某些类型类上下文中,这可能是错误的选择。

例如:(人为的例子)

foo :: Read a => String -> ...
foo s = let
   x = read s && True
   in ...

使用Read a约束来解析read s是错误的。实际上,为此,我们需要使用全局实例Read Bool,而忽略约束。也许稍后在代码中还有一个read s调用,我们确实需要约束,但是在这里我们不能提交约束。

要修复代码,您必须明确强制性,告诉GHC您确实要使用约束。例如,以下类型检查(ScopedTypeVariables启用)。

f :: forall a1 a2 c . (Coercible a1 c, Coercible a2 a1) => a2 -> c
f = coerce . (coerce :: a2 -> a1)

现在我们告诉GHC,中间类型确实是我们的a1

您可以通过添加类似的类型注释来修复代码。