我正在尝试做在 Haskell 中必须非常明显的事情,即 go fromJust [1]
和Just [2]
to Just [1, 2]
。但是我在网上找不到任何东西,因为我一直在寻找相关但无用的页面。那么,您如何实现这一目标?
4 回答
您可以使用liftA2 (++)
:
liftA2 (++) :: Maybe [a] -> Maybe [a] -> Maybe [a]
liftA2
只是将二进制函数提升为Applicative
. Applicative
s 是为在上下文中提升任意参数的函数而设计的,因此它们非常适合。在这种情况下,Applicative
我们使用的是Maybe
. 要了解这是如何工作的,我们可以查看定义:
liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c
liftA2 f a b = f <$> a <*> b
(<$>)
只是将纯值上的任何函数提升到一个在内部运行的函数f
: (a -> b) -> f a -> f b
。fmap
(如果您熟悉 s,它只是 的别名Functor
。)对于Maybe
:
_ <$> Nothing = Nothing
f <$> Just x = Just (f x)
(<*>)
有点棘手:它将内部的函数应用于内部f
的值f
: f (a -> b) -> f a -> f b
。对于Maybe
:
Just f <*> Just x = Just (f x)
_ <*> _ = Nothing
(实际上,与 ,f <$> x
是一样的pure f <*> x
,Just f <*> x
用于Maybe
。)
因此,我们可以扩展 的定义liftA2 (++)
:
liftA2 (++) a b = (++) <$> a <*> b
-- expand (<$>)
liftA2 (++) (Just xs) b = Just (xs ++) <*> b
liftA2 (++) _ _ = Nothing
-- expand (<*>)
liftA2 (++) (Just xs) (Just ys) = Just (xs ++ ys)
liftA2 (++) _ _ = Nothing
事实上,我们可以使用这些运算符将任意数量的参数的函数提升为 any Applicative
,只需遵循 的模式liftA2
。这称为applicative style,在惯用的 Haskell 代码中很常见。(++) <$> a <*> b
在这种情况下,通过编写, ifa
和b
已经是变量来直接使用它甚至可能更惯用。(另一方面,如果你部分应用它——比如说,将它传递给一个高阶函数——那么liftA2 (++)
最好。)
Every Monad
is an Applicative
,所以如果你发现自己试图将一个函数“提升”到一个上下文中,Applicative
可能就是你要找的。
虽然@ehird 的回答很棒,但我会使用以下形式的 noobish 解决方案:
mergeJust a b = do
a' <- a
b' <- b
return (a' ++ b')
要将解决方案扩展到Just
s 列表,您可以使用
fmap join $ sequence [Just[1],Just[2],Just[3]]
-- Just [1,2,3]
由于其他解决方案中没有提到,我就在这里说一下。在我看来,完成任务的最简单方法是使用<>
(或mappend
) from Data.Monoid
。
import Data.Monoid
Just [1,2] <> Just [7,8] == Just [1,2,7,8]
但是,请注意,与 ehird 的应用解决方案不同,此解决方案不会短路Nothing
值。
Just [1,2] <> Nothing ---> Just [1,2]
--However
(++) <$> Just [1,2] <*> Nothing ---> Nothing