我尝试遵循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。)但是当我反复戳它时,fw
和bw
并不构成:
λ :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'
—即使我在类型签名中指出它们应该相同!不管我使用fw
和bw
加上什么类型的注释,它们都不想统一。
如果我从fw
中删除了类型签名,那么一切都会顺利进行。特别是,推断出的类型签名将是:
fw :: ((a -> a) -> t) -> t
因此,看来forall
量词破坏了事物。但是我不明白为什么。这不是说“任何类型的a -> a'
,包括a -> a
” 都可以吗?似乎相同的类型同义词G f a
在fw
和bw
的类型签名中以不同的方式起作用!
这是怎么回事?
更多实验:
λ (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
并非如此。为什么?
答案 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
。
要使用它,需要根据需要利用同构G
和unG
,并根据需要进行包装和展开。这需要程序员额外的工作,但是正是这项工作使推理算法得以完成其工作。
或者,不要使用.
,而要使用点对点的风格来编写函数
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'
由于现在所有的量词都位于顶层,因此在将bw
与bw . h
或h . 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
的函数。