显示"newtype T a = T (a -> Int)"是一个不是函子的类型构造函数



有人声称newtype T a = T (a -> Int)是一个不是函子的类型构造函数(但是一个逆变函子)。怎么会这样?或者什么是逆变函子(因此我认为为什么不能将其设为正常函子是显而易见的)?

给定

newtype T a = T (a -> Int)

让我们尝试为此数据类型构建Contravariant实例。

这是有问题的类型类:

class Contravariant f where
contramap :: (a -> b) -> f b -> f a

基本上,contramap类似于fmap,但不是将函数提升a -> bf a -> f b,而是将其提升到f b -> f a

让我们开始编写实例...

instance Contravariant T where
contramap g (T f) = ?

在我们填写?之前,让我们考虑一下gf的类型是什么:

g :: a -> b
f :: b -> Int

为了清楚起见,我们不妨提到

f a ~ T (a -> Int)
f b ~ T (b -> Int)

因此,我们可以按如下方式填写?

instance Contravariant T where
contramap g (T f) = T (f . g)

为了超级迂腐,您可以将g重命名为aToBf重命名为bToInt

instance Contravariant T where
contramap aToB (T bToInt) = T (bToInt . aToB)

您可以为T a编写Contravariant实例的原因归结为这样一个事实,即aT (a -> Int)中处于逆变位置。说服自己T a不是Functor的最好方法是尝试(并失败)自己编写Functor实例。

假设 T 是一个函子。然后我们有

fmap :: (a -> b) -> T a -> T b

现在,让我们尝试实现它。

instance Functor T where
fmap f (T g) = T $ x -> _y

显然,我们从这样的东西开始,并以某种方式组合fgx的值,以计算正确类型的y值。我们该怎么做呢?好吧,请注意,我称之为_y,它告诉GHC我需要一些帮助来弄清楚放什么。全康有什么要说的?

<interactive>:7:28: error:
• Found hole: _y :: Int
Or perhaps ‘_y’ is mis-spelled, or not in scope
• In the expression: _y
In the second argument of ‘($)’, namely ‘ x -> _y’
In the expression: T $  x -> _y
• Relevant bindings include
x :: b (bound at <interactive>:7:23)
g :: a -> Int (bound at <interactive>:7:13)
f :: a -> b (bound at <interactive>:7:8)
fmap :: (a -> b) -> T a -> T b (bound at <interactive>:7:3)

所以现在我们清楚了所有相关事物的类型,对吧?我们需要以某种方式返回一个Int,我们必须构建它是:

x :: b
g :: a -> Int
f :: a -> b

好吧,好吧,我们唯一可能创建Int的东西是g,所以让我们填写一下,将g的论点留空以寻求GHC的更多帮助:

instance Functor T where
fmap f (T g) = T $ x -> g _y
<interactive>:7:31: error:
• Found hole: _y :: a
Where: ‘a’ is a rigid type variable bound by
the type signature for:
fmap :: forall a b. (a -> b) -> T a -> T b
at <interactive>:7:3
Or perhaps ‘_y’ is mis-spelled, or not in scope
• In the first argument of ‘g’, namely ‘(_y)’
In the expression: g (_y)
In the second argument of ‘($)’, namely ‘ x -> g (_y)’
• Relevant bindings include
x :: b (bound at <interactive>:7:23)
g :: a -> Int (bound at <interactive>:7:13)
f :: a -> b (bound at <interactive>:7:8)
fmap :: (a -> b) -> T a -> T b (bound at <interactive>:7:3)

好吧,我们可以自己预测:要调用g,我们需要一个来自某处的a类型的值。但是我们在作用域中没有任何a类型的值,也没有任何返回a类型值的函数!我们陷入了困境:不可能产生我们现在想要的值,尽管在每一步我们都做了唯一可能的事情:没有什么可以备份和尝试不同的。

为什么会这样?因为如果我给你一个类型a -> Int的函数并说"但顺便说一下,这里有一个来自a -> b的函数,请给我一个来自b -> Int的函数",你实际上不能a -> b使用该函数,因为没有人给你任何a来调用它!如果我给你一个b -> a的功能,那会很有帮助,对吧?然后,你可以从b -> Int生成一个函数,首先调用b -> a函数来获取a,然后调用原始a -> Int函数来获取所需的Int

这就是逆变函子的意义所在:我们反转传递给fmap的函数中的箭头,因此它可以映射你"需要"的东西(函数参数)而不是你"拥有"的东西(具体值,函数的返回值等)。


旁白:我之前声称我们在每一步都做了"唯一可能的事情",这有点虚构。我们不能用fgx来构建一个Int,但我们当然可以凭空编造各种数字。我们对类型b一无所知,所以我们无法以某种方式获得从它派生的Int,但我们可以说"让我们总是返回零",这在技术上满足类型检查器:

instance Functor T where
fmap f (T g) = T $ const 0

显然,这看起来很不对劲,因为它似乎fg应该非常重要,而我们忽略了它们!但它是类型检查,所以我们没事,对吧?

不,这违反了函子定律之一:

fmap id = id

我们可以很容易地证明这一点:

fmap id (T $ const 5) = (T $ const 0) /= (T $ const 5)

现在我们真的尝试了一切:我们必须在不使用我们的b类型的情况下构建Int的唯一方法是无中生有,所有这些用途都将与使用const同构,这将违反函子定律。

这是另一个观点。正如利米纳利什特所表明的那样,TContravariant。关于协变和逆变的类型,我们能说些什么?

import Data.Void

change1, change1', change2 :: (Functor f, Contravariant f) => f a -> f b
change1 = contramap (const ()) . fmap (const ())
change1' = (() >$) . (() <$)
change2 = fmap absurd . contramap absurd

前两个实现基本上是相同的(change1'change1的优化);它们中的每一个都使用()Hask的"终端对象"的事实。 相反,change2使用了Void是"初始对象"的事实。

这些函数中的每一个都用b替换了f a中的所有a,而对ab或它们之间的关系一无所知,而其他一切都保持不变。可能应该清楚的是,这意味着f a并不真正依赖于a。也就是说,f的参数必须是幻像。T不是这样,所以它不能也是协变的。

相关内容

最新更新