Вы можете использовать liftA2 (++)
:
liftA2 (++) :: Maybe [a] -> Maybe [a] -> Maybe [a]
liftA2
просто поднимает двоичную функцию в Applicative
.Applicative
были разработаны для подъема функций произвольных аргументов в контексте, поэтому они идеально подходят для этого.В этом случае 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
, если вы знакомы с 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
Действительно, мы можем использовать эти операторы, чтобы поднять функцию из любого количества аргументов в любой Applicative
, просто следуяшаблон liftA2
.Это называется аппликативным стилем и очень часто встречается в идиоматическом коде на Haskell.В этом случае может быть даже более идиоматичным использовать его напрямую, написав (++) <$> a <*> b
, если a
и b
уже являются переменными.(С другой стороны, если вы частично применяете это - скажем, чтобы передать его в функцию более высокого порядка - тогда liftA2 (++)
предпочтительнее.)
Каждый Monad
является Applicative
,так что если вы когда-нибудь пытаетесь «поднять» функцию в контекст, Applicative
, вероятно, то, что вы ищете.