Haskell - 如何将两个 monadic Maybe 函数组合成一个函数
Haskell - How to combine two monadic Maybe functions into a single function
假设我有这两个数据记录,X 和 Y 以及以下两个函数:
f1 :: IO (Maybe [X])
f2 :: X -> IO (Maybe Y)
我需要先调用 f1
然后,对于返回列表的每个元素(存储在 IO (Maybe)
中)调用 f2
,这将导致类似于 IO (Maybe [IO (Maybe Y)])
。我怎样才能把它们组合成有意义的东西,比如
result :: Maybe (IO [Y])
或
result :: IO (Maybe [Y])
?
非常感谢您的帮助:-)
候选人可能是:
result :: IO (Maybe [Y])
result = f1 >>= fmap sequenceA . mapM f2 . concat
这里 concat
是一个函数 concat :: Foldable f => f [a] -> [a]
,它会将 Nothing
转换为空列表,将 Just xs
转换为 xs
。
然后我们可以让 mapM f2
生成 IO [Maybe a]
,然后 fmap :: Functor f => (a -> b) -> f a -> f b
with sequenceA :: (Applicative f, Traversable t) => t (f a) -> f (t a)
将 IO [Maybe Y]
转换为 IO (Maybe [Y])
。
如果列表包含一个或多个 Nothing
,sequenceA
将 return 一个 Nothing
,如果 return 一个 Just xs
列表仅包含 Just
s 和 xs
最初包含在 Just
s 中的值。
本质上是在使用fmap sequenceA . sequenceA . fmap f2
。
使用 do
语法并一次分解它:
result
和 result'
给你相同的结果。
data X = X
data Y = Y
f1 :: IO (Maybe [X])
f1 = undefined
f2 :: X -> IO (Maybe Y)
f2 = undefined
result :: IO (Maybe [Y])
result = do
f1' <- f1
case f1' of
Just f1'' -> do
let a = fmap f2 f1'' :: [IO (Maybe Y)]
let b = sequenceA a :: IO [Maybe Y]
fmap sequenceA b :: IO (Maybe [Y])
Nothing -> pure Nothing
result' :: IO (Maybe [Y])
result' = do
f1' <- f1
case f1' of
Just f1'' -> do
fmap sequenceA . sequenceA . fmap f2 $ f1''
Nothing -> pure Nothing
注意:这个答案不是很适合Haskell新手,因为涉及到monad transformer
一旦我们开始将 IO
与列表的处理和生成混合,我倾向于直接跳到 Stream
monad transformer from streaming,它允许您干净地交错 IO
的执行具有 "yielding" 值的操作将在下游消耗。在某种程度上,Stream
是一个 "effectful list",每次我们 "extract" 从中获取一个值时都会执行效果。
考虑这个版本的 f1
:
import Streaming
import qualified Streaming.Prelude as S
import Data.Foldable (fold)
f1' :: Stream (Of X) IO ()
f1' = do
mxs <- lift f1
S.each (fold mxs)
lift
将 IO a
操作提升为 Stream (Of x) a
不会产生任何结果,但 returns a
作为 "final value" 的流。 (Stream
s 在消耗时产生零个或多个值,并且 return 在它们耗尽后产生不同类型的最终值)。
Streaming.Prelude.each
接受任何可以转换为列表的内容,并且 returns 一个 Stream
产生列表的元素。基本上,它将纯列表提升为有效列表。
Data.Foldable.fold
在这里使用类型 fold :: Maybe [a] -> [a]
来摆脱 Maybe
。
这里是f2
对应的版本:
f2' :: X -> Stream (Of Y) IO ()
f2' x = do
ys <- lift (f2 x)
S.each ys
将它们组合起来非常简单
result' :: Stream (Of Y) IO ()
result' = S.for f1' f2'
使用像 Streaming.Prelude.take
这样的函数,我们可以从结果中读取一个 Y
,而不必执行下一个 Y
所需的效果。 (不过,我们确实需要一次性阅读所有 X
,因为给定的 f1
已经完成了)。
如果我们想得到所有的Y
,我们可以用Streaming.Prelude.toList_
:
result :: IO [Y]
result = S.toList_ result'
假设我有这两个数据记录,X 和 Y 以及以下两个函数:
f1 :: IO (Maybe [X])
f2 :: X -> IO (Maybe Y)
我需要先调用 f1
然后,对于返回列表的每个元素(存储在 IO (Maybe)
中)调用 f2
,这将导致类似于 IO (Maybe [IO (Maybe Y)])
。我怎样才能把它们组合成有意义的东西,比如
result :: Maybe (IO [Y])
或
result :: IO (Maybe [Y])
?
非常感谢您的帮助:-)
候选人可能是:
result :: IO (Maybe [Y])
result = f1 >>= fmap sequenceA . mapM f2 . concat
这里 concat
是一个函数 concat :: Foldable f => f [a] -> [a]
,它会将 Nothing
转换为空列表,将 Just xs
转换为 xs
。
然后我们可以让 mapM f2
生成 IO [Maybe a]
,然后 fmap :: Functor f => (a -> b) -> f a -> f b
with sequenceA :: (Applicative f, Traversable t) => t (f a) -> f (t a)
将 IO [Maybe Y]
转换为 IO (Maybe [Y])
。
Nothing
,sequenceA
将 return 一个 Nothing
,如果 return 一个 Just xs
列表仅包含 Just
s 和 xs
最初包含在 Just
s 中的值。
本质上是在使用fmap sequenceA . sequenceA . fmap f2
。
使用 do
语法并一次分解它:
result
和 result'
给你相同的结果。
data X = X
data Y = Y
f1 :: IO (Maybe [X])
f1 = undefined
f2 :: X -> IO (Maybe Y)
f2 = undefined
result :: IO (Maybe [Y])
result = do
f1' <- f1
case f1' of
Just f1'' -> do
let a = fmap f2 f1'' :: [IO (Maybe Y)]
let b = sequenceA a :: IO [Maybe Y]
fmap sequenceA b :: IO (Maybe [Y])
Nothing -> pure Nothing
result' :: IO (Maybe [Y])
result' = do
f1' <- f1
case f1' of
Just f1'' -> do
fmap sequenceA . sequenceA . fmap f2 $ f1''
Nothing -> pure Nothing
注意:这个答案不是很适合Haskell新手,因为涉及到monad transformer
一旦我们开始将 IO
与列表的处理和生成混合,我倾向于直接跳到 Stream
monad transformer from streaming,它允许您干净地交错 IO
的执行具有 "yielding" 值的操作将在下游消耗。在某种程度上,Stream
是一个 "effectful list",每次我们 "extract" 从中获取一个值时都会执行效果。
考虑这个版本的 f1
:
import Streaming
import qualified Streaming.Prelude as S
import Data.Foldable (fold)
f1' :: Stream (Of X) IO ()
f1' = do
mxs <- lift f1
S.each (fold mxs)
lift
将 IO a
操作提升为 Stream (Of x) a
不会产生任何结果,但 returns a
作为 "final value" 的流。 (Stream
s 在消耗时产生零个或多个值,并且 return 在它们耗尽后产生不同类型的最终值)。
Streaming.Prelude.each
接受任何可以转换为列表的内容,并且 returns 一个 Stream
产生列表的元素。基本上,它将纯列表提升为有效列表。
Data.Foldable.fold
在这里使用类型 fold :: Maybe [a] -> [a]
来摆脱 Maybe
。
这里是f2
对应的版本:
f2' :: X -> Stream (Of Y) IO ()
f2' x = do
ys <- lift (f2 x)
S.each ys
将它们组合起来非常简单
result' :: Stream (Of Y) IO ()
result' = S.for f1' f2'
使用像 Streaming.Prelude.take
这样的函数,我们可以从结果中读取一个 Y
,而不必执行下一个 Y
所需的效果。 (不过,我们确实需要一次性阅读所有 X
,因为给定的 f1
已经完成了)。
如果我们想得到所有的Y
,我们可以用Streaming.Prelude.toList_
:
result :: IO [Y]
result = S.toList_ result'