这对 Haskell 专业人士来说应该很容易..
我有一个 Maybe 值,
> let a = Just 5
我可以打印它:
> print a
Just 5
但我想在 Maybe 内部应用一个 I/O Action 。我想出如何在不使用
case
的情况下做到这一点的唯一方法是:> maybe (return ()) print a
5
但是,这似乎太冗长了。首先,
return ()
特定于 I/O monad,所以我必须为每个我想尝试这个技巧的 monad 想出一个不同的“零”。我想基本上将 I/O 操作(打印)映射到 Maybe 值并在它是
Just
时打印它, 或者如果是 Nothing
则不执行任何操作.我想以某种方式表达它,> fmap print a
但这不起作用,因为
print
是一个 IO Action :No instance for (Show (IO ()))
我试过
Applicative
,但不知道是否有办法表达它:> print <$> a
No instance for (Show (IO ()))
显然,我对 monads-inside-monads 有点困惑。谁能告诉我最简洁地表达这一点的正确方法?
谢谢。
最佳答案
pelotom 的回答是直截了当的。但不是那个有趣的! sequence
是 Haskell 函数,可以将其视为在列表和 monad 之间翻转类型构造函数的顺序。sequence :: (Monad m) => [m a] -> m [a]
现在你想要的是,可以说,在 Maybe
之间翻转类型构造函数的顺序。和一个单子(monad)。 Data.Traversable 导出 sequence
以这种能力发挥作用!Data.Traversable.sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)
这可以专门用于 Maybe (IO ()) -> IO (Maybe ())
就像你的例子一样。
因此:
Prelude Data.Traversable> Data.Traversable.sequence (fmap print $ Nothing)
Nothing
Prelude Data.Traversable> Data.Traversable.sequence (fmap print $ Just 123)
123
Just ()
请注意,还有一个
sequenceA
稍微更通用的函数,不仅适用于 Monads,而且适用于所有 Applicatives。那么为什么要使用这种方法呢?对于
Maybe
明确地将其分开的方法很好。但是更大的数据结构呢——Map
例如?在这种情况下,traverse
, sequenceA
和来自 Data.Traversable
的 friend 可以很方便。编辑:正如 Ed'ka 所说,
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
所以可以写traverse print $ Just 123
.
关于haskell - 单值的 fmap 有什么相似之处?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4927014/