43

在我的空闲时间我正在学习 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. 如果我们fEither a我们代替fmap :: (c -> d) -> Either a c -> Either a d

  3. is 的类型和Right (g x)isEither a (g x)的类型,所以我们有 is 的类型g x,这是我们所期望的(参见上面的 2.)dRight (g x)Either a dfmap

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

我的推理正确吗?

4

4 回答 4

24

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

于 2011-03-04T14:56:48.390 回答
14

您的帐户当然是正确的。也许我们对这样的实例有困难的原因是我们实际上一次定义了无限多个仿函数实例——每种可能的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 实例,第一个看起来很像 for 的实例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值。

于 2011-03-04T16:20:56.790 回答
5

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

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

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

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

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

更新:看看Data.Bifunctor,其中Either(,)是实例。每个双子都有两个参数,并且在每个参数中都是一个函子。这体现在Bifunctor's 方法firstsecond.

Bifunctor的定义Either非常对称:

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
于 2012-11-11T07:41:28.673 回答
1

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

在这里插入,它可能是有道理的。

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

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

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

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

换句话说, Either a 是一个函子,因为有这样一个明显的 fmap 由下式给出:

  • 适用于正确的值 f
  • 对于左值什么都不做
于 2011-03-04T18:43:51.053 回答