Лучшие практики, как оценить список Maybes - PullRequest
16 голосов
/ 17 декабря 2011

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

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

Заранее спасибо, Крис

Ответы [ 3 ]

26 голосов
/ 17 декабря 2011

Сначала вы должны превратить [Maybe a] в Maybe [a] со всеми элементами Just (получая Nothing, если любой из них равен Nothing). Это можно сделать, используя sequence , используя экземпляр Maybe's 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; это привязка , а не карта .

14 голосов
/ 17 декабря 2011

Насколько я понимаю, вы хотите получить сумму из группы майбов или 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
6 голосов
/ 17 декабря 2011

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

λ 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.

...