【问题标题】:Understanding how Either is an instance of Functor了解 Either 如何是 Functor 的实例
【发布时间】:2011-07-08 21:23:23
【问题描述】:

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

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

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

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

这是我的理解:

首先让我将上面的实例重写为

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

现在:

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

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

  3. Right (g x) 的类型是Either a (g x)g x 的类型是d,所以我们得到Right (g x) 的类型是Either a d,这就是我们所期望的fmap(见上文 2.)

  4. 1234563 . 以上):d 应该是第二个参数,而不是第一个!所以我们不能映射到Left

我的推理正确吗?

【问题讨论】:

  • 这两个可能比 Functor 更明显是 Bifunctor - Bifunctor 有一个操作 bimap - bimap :: (a -> m) -> (b -> n) -> fab -> fm n .这为您提供了左右两种情况的映射。 Haskell 的标准库没有 Bifunctor 类,这是因为 Bifunctor 比“野外”的 Functor 少得多,尽管它对 Either 和 pair (a,b) 很方便。
  • 在 3 中,您使用 (g x) 作为类型的一部分,但它是一个值。看来您打算在那里写 typeof (g x),而不是 (g x) 本身。
  • @stephen tetley:这很有趣!谢谢
  • @stephentetley 我知道你之前写过这个,但现在 Bifunctor 从 base-4.8.0.0 开始就在标准库中:hackage.haskell.org/package/base-4.8.0.0/docs/…

标签: haskell typeclass functor either


【解决方案1】:

这是对的。这种行为还有另一个非常重要的原因:您可以将Either a b 视为一个计算,它可能成功并返回b,或者失败并返回错误消息a。 (这也是 monad 实例的工作方式)。所以很自然,仿函数实例不会触及 Left 值,因为您想映射计算,如果它失败,则没有什么可操作的。

【讨论】:

  • 我还没有了解 monads :) 我读过 Either a b 以您描述的方式使用,而 a 代表错误消息:但这只是一个约定,对吧?
  • @MarcoS:正确,这只是一个约定。 Either 可以很好地解释为析取:类型为 Either a b 的值包含类型 a 的值或类型 b 的值,但不能同时包含两者。
  • 在个别情况下——“Either String String”“Either String Char”、“Either String Int”等——当然是这样;左右类型没有区别。但是作为一个仿函数,你可以在这些例子中 fmap ——作为(任一字符串)——有一个完全的不对称性。进入具体 Functor 实例(此处为 String)的类型必须表示可以在任何类型的所有普通计算中找到的通用内容(发生在右侧,通过 fmap)——所以“错误” ' 解释虽然不是唯一的,但也不是偶然的。
【解决方案2】:

您的帐户当然是正确的。也许我们对这样的实例有困难的原因是我们实际上一次定义了无限多个仿函数实例——每个可能的Left 类型都有一个。但是 Functor 实例是一种对系统中无限多种类型进行操作的系统方式。因此,我们定义了无限多种方式来系统地对系统中的无限多种类型进行操作。该实例在两个方面涉及普遍性。

不过,如果您分阶段进行,也许这并不奇怪。这些类型中的第一个是Maybe 的冗长版本,使用单位类型() 及其唯一合法值()

data MightBe b     = Nope ()    | Yep b
data UnlessError b = Bad String | Good b
data ElseInt b     = Else Int   | Value b

这里我们可能累了​​,做个抽象:

data Unless a b    = Mere a     | Genuine b

现在我们毫无问题地制作我们的 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)

但是,再一次,为什么要麻烦,让我们进行抽象:

instance Functor (Unless a) where
  fmap f (Mere a) = Mere a
  fmap f (Genuine x) = Genuine (f x)

Mere a 术语未被触及,()StringInt 值未被触及。

【讨论】:

  • 谢谢,这也是一个很好的解释。但是,我接受了 FUZxxl 的帖子作为答案,因为除了确认我的推理之外,它还给了我一个有趣的提示,将 Either a b 解释为计算(monad)
  • 这是一个很好的答案。我特别感谢您的第一段,它解释了双重普遍性。
【解决方案3】:

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

newtype FlipEither b a = FlipEither { unFlipEither :: Either a b }

所以我们有构造函数FlipEither :: Either a b -> FlipEither b a,它用交换的类型参数将Either 包装到我们的newtype 中。我们有 dectructor 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)

请注意,如果我们暂时忘记FlipEither,我们只会得到EitherFunctor 的定义,只是交换了Left/Right。现在,每当我们在Either 的第一个类型参数中需要Functor 实例时,我们可以将值包装到FlipEither 中,然后再将其解包。例如:

fmapE2 :: (a -> b) -> Either a c -> Either b c
fmapE2 f = unFlipEither . fmap f . FlipEither

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

EitherBifunctor的定义很对称:

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

【讨论】:

    【解决方案4】:

    现在,我试图理解为什么 在 a 的情况下实现映射 正确的值构造函数,但没有 在左派的情况下?

    在此处插入,它可能有意义。

    假设 a = String(错误消息) 您将 Either a 应用于浮点数。

    所以你有一个 f: Float -> Integer 比如说四舍五入。

    (任意字符串)(浮点数)= 任意字符串浮点数。

    now (fmap f):: Either String Float -> Either String Int 那么你打算用 f 做什么呢? f 不知道如何处理字符串,所以你不能在那里做任何事情。 显然您唯一可以采取行动的是正确的值,同时保持左侧的值不变。

    换句话说,Eather a 是一个函子,因为有这样一个明显的 fmap:

    • 对于右值应用 f
    • 对于左值什么都不做

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-15
      • 2020-03-26
      • 1970-01-01
      • 1970-01-01
      • 2017-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多