2011-12-17 2 views
16

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

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

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

+0

Как насчет «продукта монады»? – adamse

+2

Я не знаю, вот почему я спрашиваю. – Chris

+0

Я расширил свой ответ, чтобы соответствовать вашему обновленному вопросу. – ehird

ответ

25

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

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

definition of sequence эквивалентно следующему:

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 expression of the monad laws, мы можем переписать это следующим образом:

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, когда список пуст, и, таким образом, иметь возможность опускать значение сгиба нуля без Worr инь об ошибках на пустых списках, то вы можете использовать эту функцию кратную:

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; это bind, а не map.

14

Как я понимаю, вы хотите получить сумму кучи майбетов или 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 
+0

Хорошее решение, хотя я предпочитаю мой, так как он может быть легко использован для не-складных тоже. Тем не менее, ваш проще, если использовать только фальцовку. – ehird

+1

С другой стороны, есть что-то симпатичное около 1, а затем 2. Но, может быть, это не лучший показатель качества кода ... –

+0

К сожалению, я чувствую, что желаемое поведение может состоять в том, чтобы возвращать 'Nothing', когда список пуст, и в этом случае потепление смещается :( – ehird

6

А что-то же просто, как:

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

+2

Только что заметил, что этот ответ уже содержит эту версию! Когда я читал ответ Эгирда, как только я увидел слово «MonadPlus», я: «Должен быть более простой способ», и я не стал продолжать: «Извините! Во всяком случае, я поддержал ответ Эгирда. – lbolla

+1

Ха-ха, ничего страшного :) Хорошая мысль о «MonadPlus», я попытался прояснить ситуацию. (Случайно удалил мой предыдущий комментарий, удерживая клавишу, oops ...) – ehird

Смежные вопросы