Лучшая практика, как оценить список Maybes

Я ищу функцию, которая принимает функцию (a → a → a) и список [Maybe a] и возвращает Maybe a. Hoogle не дал мне ничего полезного. Это похоже на довольно распространенный шаблон, поэтому я спрашиваю, есть ли лучшая практика для этого случая?

>>> f (+) [Just 3, Just 3]
Just 6
>>> f (+) [Just 3, Just 3, Nothing]
Nothing

Спасибо, Chris

Ответы

Ответ 1

Сначала вы должны превратить [Maybe a] в Maybe [a] со всеми элементами Just (при условии Nothing, если любой из них Nothing). Это можно сделать, используя sequence, используя экземпляр Maybe Monad:

GHCi> sequence [Just 1, Just 2]
Just [1,2]
GHCi> sequence [Just 1, Just 2, Nothing]
Nothing

определение последовательности эквивалентно следующему:

sequence [] = return []
sequence (m:ms) = do
  x <- m
  xs <- sequence ms
  return (x:xs)

Итак, мы можем развернуть последний пример следующим образом:

do x <- Just 1
   xs <- do
       y <- Just 2
       ys <- do
           z <- Nothing
           zs <- return []
           return (z:zs)
       return (y:ys)
   return (x:xs)

Используя выражение do-notation законов монады, мы можем переписать это следующим образом:

do x <- Just 1
   y <- Just 2
   z <- Nothing
   return [x, y, z]

Если вы знаете, как работает монада Maybe, вы должны теперь понять, как sequence работает для достижения желаемого поведения.:)

Затем вы можете скомпоновать это с помощью foldr с помощью (<$>) (из Control.Applicative, эквивалентно, fmap или liftM), чтобы свернуть вашу двоичную функцию по списку:

GHCi> foldl' (+) 0 <$> sequence [Just 1, Just 2]
Just 3

Конечно, вы можете использовать любую фальцу, какую хотите, например foldr, foldl1 и т.д.

В качестве дополнительного, если вы хотите, чтобы результат был Nothing, когда список пуст, и, таким образом, он может опустить нулевое значение складки, не беспокоясь о ошибках в пустых списках, тогда вы можете использовать эту функцию сгиба

mfoldl1' :: (MonadPlus m) => (a -> a -> a) -> [a] -> m a
mfoldl1' _ [] = mzero
mfoldl1' f (x:xs) = return $ foldl' f x xs

и аналогично для foldr, foldl и т.д. Для этого вам нужно импортировать Control.Monad.

Однако это нужно использовать несколько иначе:

GHCi> mfoldl1' (+) =<< sequence [Just 1, Just 2]
Just 3

или

GHCi> sequence [Just 1, Just 2] >>= mfoldl1' (+)
Just 3

Это связано с тем, что в отличие от других сгибов тип результата выглядит как m a вместо a; это привязка, а не карта.

Ответ 2

Как я понимаю, вы хотите получить сумму кучки maybes или Nothing, если они есть Nothing. Это на самом деле довольно просто:

maybeSum = foldl1 (liftM2 (+))

Вы можете обобщить это на что-то вроде:

f :: Monad m => (a -> a -> a) -> [m a] -> m a
f = foldl1 . liftM2

При использовании с монадой Maybe f работает точно так, как вы хотите.

Если вам нужны пустые списки, вы можете использовать эту версию:

f :: MonadPlus m => (a -> a -> a) -> [m a] -> m a
f _ []      = mzero
f fn (x:xs) = foldl (liftM2 fn) x xs

Ответ 3

Как насчет чего-то простого:

λ Prelude > fmap sum . sequence $ [Just 1, Just 2]
Just 3
λ Prelude > fmap sum . sequence $ [Just 1, Just 2, Nothing]
Nothing

Или, используя (+):

λ Prelude > fmap (foldr (+) 0) . sequence $ [Just 1, Just 2]
Just 3
λ Prelude > fmap (foldr (+) 0) . sequence $ [Just 1, Just 2, Nothing]
Nothing

Итак, maybeSum = fmap sum . sequence.