为什么此类型注释错误?

时间:2018-07-29 05:53:29

标签: haskell types category-theory isomorphism higher-rank-types

我尝试遵循article by Gabriel Gonzalez,但遇到类型不匹配的情况。考虑以下简短模块:

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE Rank2Types #-}

module Yoneda where

newtype F a = F a deriving (Show, Functor)

type G f a = forall a'. (a -> a') -> f a'

fw :: Functor f => G f a -> f a
fw x = x id

bw :: Functor f => f a -> G f a
bw x = \f -> fmap f x

它可以编译。 (使用ghc 8.2.2和8.4.3。)但是当我反复戳它时,fwbw并不构成:

λ :t bw . fw

<interactive>:1:6: error:
    • Couldn't match type ‘a’ with ‘G f a1’
      ‘a’ is a rigid type variable bound by
        the inferred type of it :: Functor f => a -> (a1 -> a') -> f a'
        at <interactive>:1:1
      Expected type: a -> f a1
        Actual type: G f a1 -> f a1
    • In the second argument of ‘(.)’, namely ‘fw’
      In the expression: bw . fw

当我更仔细地观察bw时,它所收取和返回的函子的类型似乎是不同的:

λ :t bw
bw :: Functor f1 => f2 a1 -> (a2 -> a') -> f1 a'

—即使我在类型签名中指出它们应该相同!不管我使用fwbw加上什么类型的注释,它们都不想统一。

如果我从fw中删除了类型签名,那么一切都会顺利进行。特别是,推断出的类型签名将是:

fw :: ((a -> a) -> t) -> t

因此,看来forall量词破坏了事物。但是我不明白为什么。这不是说“任何类型的a -> a',包括a -> a都可以吗?似乎相同的类型同义词G f afwbw的类型签名中以不同的方式起作用!

这是怎么回事?


更多实验:

λ (fw . bw) (F 1)
...error...
λ (fw (bw (F 1)))
F 1
λ :t fw . undefined
...error...
λ :t undefined . fw
...error
λ :t bw . undefined
bw . undefined :: Functor f => a1 -> (a2 -> a') -> f a'
λ :t undefined . bw
undefined . bw :: Functor f => f a -> c

所以(如@chi在答案中指出的那样) fw不能构成函数。但是bw并非如此。为什么?

2 个答案:

答案 0 :(得分:3)

这是一个可预测性问题。

本质上,如果我们有一个多态值f :: forall a . SomeTypeDependingOn a,并且在更大的表达式中使用它,则可以将类型a实例化为适合该上下文的任何类型T。但是,谓词性要求T不包含forall。 需要进行此限制才能进行类型推断。

在您的代码中,bw . fw使用多态函数.(组成)。它具有多态类型,其中一个类型变量t表示要组合的第二个函数的域(g中的f . g)。要让bw . fw键入check,我们应该选择t ~ G f a,但是选择G f a = (forall a' . ...)会违反谓语。

通常的解决方案是使用newtype包装器

newtype G f a = G { unG :: forall a'. (a -> a') -> f a' }

在构造函数下“隐藏” forall,从而允许t ~ G f a。 要使用它,需要根据需要利用同构GunG,并根据需要进行包装和展开。这需要程序员额外的工作,但是正是这项工作使推理算法得以完成其工作。

或者,不要使用.,而要使用点对点的风格来编写函数

test :: Functor f => G f a -> G f a
test x = bw (fw x)

关于GHCi报告的bw的类型:

> :t bw
bw :: Functor f1 => f2 a1 -> (a2 -> a') -> f1 a'

此类型是“ forall吊装”的结果,它实际上以这种方式“移动”通用量词:

a1 -> ... -> forall b. F b   =====>     forall b. a1 -> ... -> F b

自动执行吊装,以帮助键入推论。

更多失败,我们有

bw :: forall f a . Functor f => f a -> G f a
-- by definition of G
bw :: forall f a . Functor f => f a -> (forall a'. (a -> a') -> f a')
-- after hoisting
bw :: forall f a a'. Functor f => f a -> (a -> a') -> f a'

由于现在所有的量词都位于顶层,因此在将bwbw . hh . bw中的另一个函数组成时,我们可以首先将f, a, a'实例化为新类型变量,然后对这些变量执行统一操作,以匹配h的类型。

例如,在bw . undefined中,我们按以下步骤进行操作

 -- fresh variables for (.)
 (.) :: (b -> c) -> (a -> b) -> a -> c
 -- fresh variables for bw
 bw :: Functor f . f a1 -> (a1 -> a') -> f a'
 -- fresh variables for undefined
 undefined :: a2

 So we get:
 b = f a1
 c = (a1 -> a') -> f a'
 a2 = a -> b

 Hence the type of (bw . undefined) is
 a -> c
 = a -> (a1 -> a') -> f a'
 (assuming Functor f)

GHCi同意,只是它为类型变量选择了不同的名称。当然,这种选择并不重要。

(bw . undefined) :: Functor f => a1 -> (a2 -> a') -> f a'

啊哈! GHCi-8.2.2似乎存在一些问题,GHC 8.4.3中没有该问题。

-- GHCi 8.2.2
> :set -XRankNTypes
> type G f a = forall a'. (a -> a') -> f a'
> bw :: Functor f => f a -> G f a ; bw x = \f -> fmap f x
> :t bw
bw :: Functor f1 => f2 a1 -> (a2 -> a') -> f1 a'

-- GHCi 8.4.3
> :set -XRankNTypes
> type G f a = forall a'. (a -> a') -> f a'
> bw :: Functor f => f a -> G f a ; bw x = \f -> fmap f x
> :t bw
bw :: Functor f => f a -> (a -> a') -> f a'

答案 1 :(得分:0)

a == b量词使您的类型接受所有forall,但是您在a -> a'中实际需要的是一个限制,以确保参数类型和返回的类型相同,并且签名fw是什么意思。

是的,a -> a版本接受功能forall,但它不仅接受此类功能。如上所述,编译器告诉您a -> a仅应接受类型为fw的函数。

相关问题