为什么我不能使用 Haskell 中的 id 创建 Functor 的 Either 实例?

Why can't I make Either instance of Functor using id in Haskell?

在自定义EitherFunctor时,为了更清楚地理解类型和类型类,我发现了以下情况:

Functor

module Functor (Functor, fmap) where

import Prelude hiding(Functor, fmap)

class Functor f where
  fmap :: (a -> b) -> f a -> f b

Either

module Either(Either(..)) where
import Prelude hiding(Either(..), Functor, fmap)

data Either a b = Left a | Right b deriving(Show)

instance Functor (Either a) where
  fmap f (Right x) = Right (f x)
  fmap _ (Left x) = Left x

上面显示的代码可以正常编译,但是,如果我将其更改为使用 id,它不会编译:

instance Functor (Either a) where
  fmap f (Right x) = Right (f x)
  fmap _ = id

为什么??我错过了什么?以下代码也不起作用:

instance Functor (Either a) where
  fmap f (Right x) = Right (f x)
  fmap f all@(Left x) = all

...这在我看来很奇怪,因为下面显示的代码可以编译:

data Shape = Circle Point Float | Rectangle Point Point deriving (Show)

data Point = Point Float Float deriving (Show)

test :: Shape -> String
test (Circle _ x) = show x
test all@(Rectangle _ x) = show all ++ " - "++ show x

提前致谢

让我们看一下专门用于 Either 函子的 fmap 类型:

fmap :: (a -> b) -> Either e a -> Either e b

由此可见,在fmap f all@(Left _)中,all的类型是Either e a。这与 fmap 的签名规定的预期结果类型 Either e b 不匹配,因此 fmap f all@(Left _) = all 类型不正确。

与使用 id 的情况类似。

你想做的事情归结为:

f :: Either a Bool -> Either a ()
f (Right _) = Right ()
f left = left

错误:

foo.hs:3:7:
    Couldn't match type ‘Bool’ with ‘()’
    Expected type: Either a ()
      Actual type: Either a Bool
    In the expression: left
    In an equation for ‘f’: f left = left
Failed, modules loaded: none.

left 绑定到函数参数。所以类型检查器知道它是 Either a Bool 类型。然后它被用作 return 值。我们从 f :: Either a Bool -> Either a () 类型知道 return 值必须是 Either a () 类型。如果 left 是一个有效的 return 值,它的类型必须匹配 f 的 return 类型。所以 Either a () 必须等于 Either a Bool;它不是,所以类型检查器拒绝该程序。

反过来,基本上是同一个问题:

λ let l = Left () :: Either () ()
l :: Either () ()

λ l
Left ()
it :: Either () ()

λ l :: Either () Bool

<interactive>:10:1:
    Couldn't match type ‘()’ with ‘Bool’
    Expected type: Either () Bool
      Actual type: Either () ()
    In the expression: l :: Either () Bool
    In an equation for ‘it’: it = l :: Either () Bool

我们给了 l 绑定和类型,然后尝试将其用作不同的类型。这是无效的(并且通过 id 提供它也不会改变它的类型)。尽管 Left () 对于 Either () Bool 类型的值也是有效的 源代码文本 ,但这并不意味着已知的特定值属于 [=可以用源文本 Left () 定义的 26=] 可以像 Either () Bool.

类型一样使用

如果你有一个多态值,你可以这样做:

λ let l = Left ()
l :: Either () b

λ l :: Either () ()
Left ()
it :: Either () ()

λ l :: Either () Bool
Left ()
it :: Either () Bool

注意这里原来的l值在b中是多态的;它可以用作 any b.

Either () b

但是您的 fmap 情况略有不同。 函数fmapb中是多态的,但是其参数的值是"within the scope of the polymorphism";在你提出论点时,fmap 的调用者已将类型 b 选择为某种特定类型 ,因此它是 "some unknown type that could by anything" 而不是 "any type I feel like choosing".无法以某种方式将 Either a b 类型的值转换为 Either a c 类型的值,因此您必须提取 a 值,然后创建包含它的 Either a c .

在解释类型错误方面,我对前两个答案没有任何补充,但我想提一下 Left x :: Either t a 在内存中的表示方式与 Left x :: Either t b 相同.这意味着即使类型系统不允许您使用 Either t a 代替类型 Either t b 的值,出于其他答案已经非常清楚地解释的原因,您可以 "force" 它通过类型检查器使用 unsafeCoerce:

import Unsafe.Coerce (unsafeCoerce)

instance Functor (Either t) where
  fmap f (Right a) = Right (f a)
  fmap f l         = unsafeCoerce l

虽然 unsafeCoerce 通常被认为是应该避免的事情,但如果您知道自己在做什么,并且有正当理由这样做,例如表现,unsafeCoerce 可以有助于让编译器知道您确定运行时值将匹配预期的结构。

在这种情况下,如果没有 unsafeCoerce,并且不考虑任何潜在的 GHC 优化,fmap f (Left x) = Left x 总是会构造一个新的但物理上相同的 Left 值,而 unsafeCoerce flavor 只是 return 原始 Left 值,没有额外的内存分配。