了解Either如何是Functor的一个实例

Mar*_*coS 36 haskell functor typeclass either

在我的空闲时间,我正在学习Haskell,所以这是一个初学者的问题.

在我的阅读中,我遇到了一个例子,说明如何Either a成为一个实例Functor:

instance Functor (Either a) where
    fmap f (Right x) = Right (f x)
    fmap f (Left x) = Left x
Run Code Online (Sandbox Code Playgroud)

现在,我试图理解为什么实现在Right值构造函数的情况下映射,但是在不是的情况下Left

这是我的理解:

首先让我重写上面的例子

instance Functor (Either a) where
    fmap g (Right x) = Right (g x)
    fmap g (Left x) = Left x
Run Code Online (Sandbox Code Playgroud)

现在:

  1. 我知道 fmap :: (c -> d) -> f c -> f d

  2. 如果我们替换f,Either a我们得到fmap :: (c -> d) -> Either a c -> Either a d

  3. 类型Right (g x)Either a (g x),和类型g xd,所以我们有一个类型Right (g x)Either a d,这是我们期望从fmap(见上述2)

  4. 现在,如果我们看一下,Left (g x)我们可以使用相同的推理来说它的类型是Either (g x) b,也就是说Either d b,它不是我们所期望的fmap(参见上面的2.):d应该是第二个参数,而不是第一个!所以我们无法映射Left.

我的推理是否正确?

fuz*_*fuz 21

这是正确的.此行为还有另一个非常重要的原因:您可以将其Either a b视为可能成功的计算,并返回b或失败并显示错误消息a.(这也是monad实例的工作方式).因此,函数实例不会触及Left值,这是很自然的,因为您想要映射计算,如果失败,则无需操作.

  • 在个别情况下——“Either String String”、“Either String Char”、“Either String Int”等——当然是这样;左右类型之间没有区别。但是作为函子,您可以 fmap over - 作为 (Either String),在这些示例中 - 存在完全不对称。进入具体 Functor 实例(此处为 `String`)的类型必须表示可以在任何类型的所有普通计算中找到的通用内容(发生在右侧,通过 `fmap`)——因此是一个“错误” ’虽然不是唯一的解释,但并非偶然。 (2认同)

app*_*ive 12

您的帐户当然是对的.也许我们对这样的实例有困难的原因是我们实际上一次定义了无限多个函子实例 - 每个可能Left类型一个.但Functor实例是在系统中无限多种类型上运行的系统方法.因此,我们定义了无数多种系统地操作系统中无限多种类型的方法.该实例涉及两种方式的一般性.

但是,如果你分阶段采取它,也许它不是那么奇怪.这些类型中的第一个是Maybe使用单位类型()及其唯一合法值的longwinded版本():

data MightBe b     = Nope ()    | Yep b
data UnlessError b = Bad String | Good b
data ElseInt b     = Else Int   | Value b
Run Code Online (Sandbox Code Playgroud)

在这里,我们可能会厌倦并做一个抽象:

data Unless a b    = Mere a     | Genuine b
Run Code Online (Sandbox Code Playgroud)

现在我们无问题地制作Functor实例,第一个看起来很像实例Maybe:

instance Functor MightBe where
  fmap f (Nope ()) = Nope ()   -- compare with Nothing
  fmap f (Yep x)   = Yep (f x) -- compare with Just (f x)

instance Functor UnlessError where
  fmap f (Bad str) = Bad str   -- a more informative Nothing
  fmap f (Good x)  = Good (f x)

instance Functor ElseInt where
  fmap f (Else n) = Else n 
  fmap f (Value b) = Value (f b)
Run Code Online (Sandbox Code Playgroud)

但是,再次,为什么要打扰,让我们进行抽象:

instance Functor (Unless a) where
  fmap f (Mere a) = Mere a
  fmap f (Genuine x) = Genuine (f x)
Run Code Online (Sandbox Code Playgroud)

Mere a条款将不会受到影响,因为(),StringInt值均未触及.


Pet*_*lák 5

正如其他人提到的那样,Either类型在这两个参数中都是一个函子。但是在Haskell中,我们只能(直接)在类型的最后一个参数中定义函子。在这种情况下,我们可以使用newtypes 来解决限制:

newtype FlipEither b a = FlipEither { unFlipEither :: Either a b }
Run Code Online (Sandbox Code Playgroud)

因此,我们有一个构造函数FlipEither :: Either a b -> FlipEither b a,将Either我们newtype的交换类型参数包装为。而且我们有解压缩器将unFlipEither :: FlipEither b a -> Either a b其解开。现在,我们可以在FlipEither的最后一个参数(实际上是Either的第一个参数)中定义函子实例:

instance Functor (FlipEither b) where
    fmap f (FlipEither (Left x))  = FlipEither (Left (f x))
    fmap f (FlipEither (Right x)) = FlipEither (Right x)
Run Code Online (Sandbox Code Playgroud)

请注意,如果忘记FlipEither了一会儿,我们只获得了/ 的Functorfor 定义。现在,每当我们需要第一个类型参数中的实例时,我们都可以将值包装到其中,然后再将其解包。例如:EitherLeftRightFunctorEitherFlipEither

fmapE2 :: (a -> b) -> Either a c -> Either b c
fmapE2 f = unFlipEither . fmap f . FlipEither
Run Code Online (Sandbox Code Playgroud)

更新:查看Data.Bifunctor,其中Either(,)是实例。每个bifunctor都有两个参数,并且每个参数都是一个函子。这反映在Bifunctor的方法first和中second

的定义BifunctorEither是非常对称的:

instance Bifunctor Either where
    bimap f _ (Left a)  = Left (f a)
    bimap _ g (Right b) = Right (g b)

    first  f = bimap f id

    second f = bimap id f
Run Code Online (Sandbox Code Playgroud)