这对Haskell专业人员来说应该很容易..
我有一个可能值,
> let a = Just 5
我可以打印出来:
> print a
Just 5
但我想将一个I / O动作应用于Maybe的内部。我在不使用case
的情况下弄清楚如何执行此操作的唯一方法是:
> maybe (return ()) print a
5
然而,这似乎太冗长了。首先,return ()
特定于I / O monad,所以我必须为每个我想尝试这个技巧的monad提出一个不同的“零”。
我想基本上将I / O操作(打印)映射到Maybe值,如果是Just
则打印它,或者如果它是Nothing
则不执行任何操作。我想以某种方式表达它,
> fmap print a
但是这不起作用,因为print
是一个IO动作:
No instance for (Show (IO ()))
我试过了Applicative
,但无法弄清楚是否有办法表达它:
> print <$> a
No instance for (Show (IO ()))
显然我对monads-inside-monads有点困惑..谁能告诉我最简洁的表达方式?
感谢。
答案 0 :(得分:23)
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,还适用于所有Applicative。
那么为什么要使用这种方法呢?对于Maybe
,明确区分的方法很好。但是更大的数据结构呢?例如Map
?在这种情况下,traverse
,sequenceA
和来自Data.Traversable
的朋友可以非常方便。
编辑:正如Ed'ka所说,traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
所以可以写traverse print $ Just 123
。
答案 1 :(得分:16)
首先,return()特定于I / O monad,所以我必须为每个我想尝试这个技巧的monad提出一个不同的“零”。
return ()
实际上非常通用,可以从其类型中看出:
Prelude> :t return ()
return () :: (Monad m) => m ()
我认为maybe (return ()) print a
方法没有错。
答案 2 :(得分:2)
但我想将一个I / O动作应用于Maybe。
的内部
这可以通过monad变换器来实现。
MaybeT是一个可以缠绕另一个monad的monad。换句话说,MaybeT可以使用任何其他Monad来抽象(无辜的[1])计算中的失败。
可悲的是,GHCi没有(2011年)有任何功能可以让你更容易玩monad变形金刚,但是你走了:
> :m + Control.Monad.Maybe Control.Monad.Trans
> let a = Just 5
> runMaybeT$ do { v <- MaybeT$ return a ; liftIO$ print v }
5
Just ()
为了更深入地了解monad和monad变换器,我建议你阅读网络上的其他资料。请记住,monads也只是包装值。
我会尽量保持简单。签名:m = IO,a =整数
runMaybeT :: MaybeT m a - &gt; m(可能是a) - 将MaybeT IO中的计算转换为IO中的计算。
执行 { - 在没有缩进的情况下使用符号使其适合ghci的提示[2]。
MaybeT :: m(也许是) - &gt; MaybeT m a - 包装类型IO的计算(可能是整数)。
返回 :: IO(Just Integer) - 将其替换为您的计算。
提升 - 在包装的monad中运行计算。[3]
Just() - 计算结果。只要不是(),GHCi就会打印IO结果。
MaybeT未包含在mtl中,因此您可能需要安装它
cabal install MaybeT
或考虑[1]
[1]也用于传递错误消息,请使用MonadError
[2]我知道GHCi中的多行输入
[3]如果你需要来自一堆monad的IO,请使用 liftIO 。
答案 3 :(得分:0)
你试过这个吗?
unwrap :: (Show a) => Maybe a -> IO ()
unwrap Nothing = return ()
unwrap (Just a) = print a
在打开数据后,它将返回/打印提交的数据。