在我的空闲时间,我正在学习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
现在:
我知道fmap :: (c -> d) -> f c -> f d
如果我们将f
替换为Either a
,我们会fmap :: (c -> d) -> Either a c -> Either a d
Right (g x)
的类型为Either a (g x)
,g x
的类型为d
,因此我们认为Right (g x)
的类型为Either a d
,这是我们对fmap
的期望(见上文2.)
现在,如果我们查看Left (g x)
,我们可以使用相同的推理来说明其类型为Either (g x) b
,即Either d b
,这不是我们所期望的fmap
(见上文2.):d
应该是第二个参数,而不是第一个参数!因此,我们无法映射Left
。
我的推理是否正确?
答案 0 :(得分:20)
这是对的。此行为还有另一个非常重要的原因:您可以将Either a b
视为计算,可能会成功并返回b
或失败并显示错误消息a
。 (这也是monad实例的工作方式)。因此,函数实例不会触及Left
值是很自然的,因为你想要映射计算,如果它失败了,就没有什么可以操作了。
答案 1 :(得分: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
在这里,我们可能会感到疲倦并做出抽象:
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
,()
和String
值,因此未触及Int
个字词。
答案 2 :(得分:4)
正如其他人所提到的,Either
类型在其两个参数中都是一个算符。但是在Haskell中,我们能够(直接)在类型的最后一个参数中定义函子。在这种情况下,我们可以使用newtype
s来解决限制:
newtype FlipEither b a = FlipEither { unFlipEither :: Either a b }
所以我们有构造函数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)
请注意,如果我们忘记FlipEither
一段时间,我们只会Functor
Either
的定义,只需交换Left
/ Right
。现在,每当我们在Functor
的第一个类型参数中需要Either
实例时,我们可以将值包装到FlipEither
中并在之后解包。例如:
fmapE2 :: (a -> b) -> Either a c -> Either b c
fmapE2 f = unFlipEither . fmap f . FlipEither
更新:查看Data.Bifunctor,其中Either
和(,)
是其中的实例。每个bifunctor都有两个参数,并且每个参数都是一个仿函数。这反映在Bifunctor
的方法first
和second
。
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
答案 3 :(得分:1)
现在,我正在努力理解为什么 在a的情况下的实现映射 正确的值构造函数,但没有 在左派的情况下?
插入此处可能有意义。
假设a = String(错误消息) 您将A或a应用于Float。
所以你有一个f:Float - >整数说例如舍入。
(字符串)(Float)= String Float。
now(fmap f):: String Float - > String Int 那你打算用f做什么? f不知道如何处理字符串,所以你不能在那里做任何事情。那是显然你可以采取的唯一行动是正确的值,同时保持左值不变。
换句话说,要么a是仿函数,因为有一个明显的fmap由下式给出: