fmap 和 bind 之间的能力差异?
Difference in capability between fmap and bind?
我是函数式编程的新手(来自 javascript),我很难说出两者之间的区别,这也扰乱了我对仿函数与 monad 的理解。
仿函数:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Monad(简体):
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
fmap
接受一个函数和一个函子,returns 一个函子。
>>=
接受一个函数和一个 monad,returns 一个 monad。
两者的区别在于函数参数:
fmap
- (a -> b)
>>=
- (a -> m b)
>>=
接受一个函数参数,returns 一个 monad。我知道这很重要,但我很难看出这一点小事如何使 monad 比函子更强大。有人可以解释一下吗?
简短的回答是,如果你能以一种有意义的方式将 m (m a)
变成 m a
,那么它就是一个 Monad。这对所有 Monad 都是可能的,但不一定对 Functors。
我认为最令人困惑的是 Functor 的所有常见示例(例如 List
、Maybe
、IO
)也是 Monad。我们需要一个 Functor 而不是 Monad 的例子。
我将使用假设的日历程序中的示例。以下代码定义了一个 Event
Functor,它存储了一些与事件相关的数据以及事件发生的时间。
import Data.Time.LocalTime
data Event a = MkEvent LocalTime a
instance Functor Event where
fmap f (MkEvent time a) = MkEvent time (f a)
Event
对象存储事件发生的时间和一些可以使用 fmap
更改的额外数据。现在让我们尝试将其设为 Monad:
instance Monad Event where
(>>=) (MkEvent timeA a) f = let (MkEvent timeB b) = f a in
MkEvent <notSureWhatToPutHere> b
我们发现我们做不到,因为您最终会得到两个 LocalTime
对象。 timeA
来自给定的 Event
和 timeB
来自 f a
的结果给出的 Event
。我们的 Event
类型被定义为只有一个 LocalTime
(time
) 出现,所以如果不把两个 LocalTime
变成一个,就不可能把它变成 Monad . (在某些情况下这样做可能是有意义的,所以如果你真的想的话,你可以把它变成一个 monad)。
暂时假设 IO
只是 Functor
,而不是 Monad
。我们如何安排两个动作的顺序?比如 getChar :: IO Char
和 putChar :: Char -> IO ()
.
我们可以尝试使用 putChar
.
映射 getChar
(执行时从标准输入读取 Char
的操作)
fmap putChar getChar :: IO (IO ())
现在我们有一个程序,它在执行时从 stdin 读取 Char
并生成一个程序,在执行时将 Char
写入 stdout。但我们真正想要的是一个程序,当执行时,从 stdin 读取 Char
并将 Char
写入 stdout。所以我们需要一个"flattening"(在IO
情况下,"sequencing")函数,类型为:
join :: IO (IO ()) -> IO ()
Functor
本身不提供这个功能。但它是 Monad
的函数,它具有更通用的类型:
join :: Monad m => m (m a) -> m a
这一切与>>=
有什么关系?碰巧的是,monadic bind 只是 fmap
和 join
:
的组合
:t \m f -> join (fmap f m)
(Monad m) => m a1 -> (a1 -> m a) -> m a
看到差异的另一种方式是 fmap
永远不会改变映射值的整体结构,但是 join
(因此 >>=
也可以)可以做到这一点。
就 IO
行为而言,fmap
将 永远不会 造成额外的 reads/writes 或其他影响。但是 join
将内部动作的 read/writes 排在外部动作之后。
嗯,(<$>)
是 fmap
的别名,而 (=<<)
与 (>>=)
相同,但参数交换:
(<$>) :: (x -> y) -> b x -> b y
(=<<) :: (x -> b y) -> b x -> b y
区别现在相当明显:使用绑定函数,我们应用的函数 return 是 b y
而不是 y
。那么这有什么区别呢?
考虑这个小例子:
foo <$> Just 3
请注意 (<$>)
会将 foo
应用于 3
,并将结果放回 Just
。也就是说,这个计算的结果不可能是Nothing
。反之:
bar =<< Just 3
这个计算可以returnNothing
。 (例如,bar x = Nothing
即可。)
我们可以用列表 monad 做类似的事情:
foo <$> [Red, Yellow, Blue] -- Result is guaranteed to be a 3-element list.
bar =<< [Red, Yellow, Blue] -- Result can be ANY size.
简而言之,对于 (<$>)
(即 fmap
),结果的 "structure" 始终与输入相同。但是使用 (=<<)
(即 (>>=)
),结果的结构可以改变。这允许条件执行、对输入做出反应以及一大堆其他事情。
我是函数式编程的新手(来自 javascript),我很难说出两者之间的区别,这也扰乱了我对仿函数与 monad 的理解。
仿函数:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Monad(简体):
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
fmap
接受一个函数和一个函子,returns 一个函子。>>=
接受一个函数和一个 monad,returns 一个 monad。
两者的区别在于函数参数:
fmap
-(a -> b)
>>=
-(a -> m b)
>>=
接受一个函数参数,returns 一个 monad。我知道这很重要,但我很难看出这一点小事如何使 monad 比函子更强大。有人可以解释一下吗?
简短的回答是,如果你能以一种有意义的方式将 m (m a)
变成 m a
,那么它就是一个 Monad。这对所有 Monad 都是可能的,但不一定对 Functors。
我认为最令人困惑的是 Functor 的所有常见示例(例如 List
、Maybe
、IO
)也是 Monad。我们需要一个 Functor 而不是 Monad 的例子。
我将使用假设的日历程序中的示例。以下代码定义了一个 Event
Functor,它存储了一些与事件相关的数据以及事件发生的时间。
import Data.Time.LocalTime
data Event a = MkEvent LocalTime a
instance Functor Event where
fmap f (MkEvent time a) = MkEvent time (f a)
Event
对象存储事件发生的时间和一些可以使用 fmap
更改的额外数据。现在让我们尝试将其设为 Monad:
instance Monad Event where
(>>=) (MkEvent timeA a) f = let (MkEvent timeB b) = f a in
MkEvent <notSureWhatToPutHere> b
我们发现我们做不到,因为您最终会得到两个 LocalTime
对象。 timeA
来自给定的 Event
和 timeB
来自 f a
的结果给出的 Event
。我们的 Event
类型被定义为只有一个 LocalTime
(time
) 出现,所以如果不把两个 LocalTime
变成一个,就不可能把它变成 Monad . (在某些情况下这样做可能是有意义的,所以如果你真的想的话,你可以把它变成一个 monad)。
暂时假设 IO
只是 Functor
,而不是 Monad
。我们如何安排两个动作的顺序?比如 getChar :: IO Char
和 putChar :: Char -> IO ()
.
我们可以尝试使用 putChar
.
getChar
(执行时从标准输入读取 Char
的操作)
fmap putChar getChar :: IO (IO ())
现在我们有一个程序,它在执行时从 stdin 读取 Char
并生成一个程序,在执行时将 Char
写入 stdout。但我们真正想要的是一个程序,当执行时,从 stdin 读取 Char
并将 Char
写入 stdout。所以我们需要一个"flattening"(在IO
情况下,"sequencing")函数,类型为:
join :: IO (IO ()) -> IO ()
Functor
本身不提供这个功能。但它是 Monad
的函数,它具有更通用的类型:
join :: Monad m => m (m a) -> m a
这一切与>>=
有什么关系?碰巧的是,monadic bind 只是 fmap
和 join
:
:t \m f -> join (fmap f m)
(Monad m) => m a1 -> (a1 -> m a) -> m a
看到差异的另一种方式是 fmap
永远不会改变映射值的整体结构,但是 join
(因此 >>=
也可以)可以做到这一点。
就 IO
行为而言,fmap
将 永远不会 造成额外的 reads/writes 或其他影响。但是 join
将内部动作的 read/writes 排在外部动作之后。
嗯,(<$>)
是 fmap
的别名,而 (=<<)
与 (>>=)
相同,但参数交换:
(<$>) :: (x -> y) -> b x -> b y
(=<<) :: (x -> b y) -> b x -> b y
区别现在相当明显:使用绑定函数,我们应用的函数 return 是 b y
而不是 y
。那么这有什么区别呢?
考虑这个小例子:
foo <$> Just 3
请注意 (<$>)
会将 foo
应用于 3
,并将结果放回 Just
。也就是说,这个计算的结果不可能是Nothing
。反之:
bar =<< Just 3
这个计算可以returnNothing
。 (例如,bar x = Nothing
即可。)
我们可以用列表 monad 做类似的事情:
foo <$> [Red, Yellow, Blue] -- Result is guaranteed to be a 3-element list.
bar =<< [Red, Yellow, Blue] -- Result can be ANY size.
简而言之,对于 (<$>)
(即 fmap
),结果的 "structure" 始终与输入相同。但是使用 (=<<)
(即 (>>=)
),结果的结构可以改变。这允许条件执行、对输入做出反应以及一大堆其他事情。