Складывание через Maybes в Haskell - PullRequest
19 голосов
/ 19 сентября 2011

Пытаясь выучить Хаскель, я столкнулся с ситуацией, в которой я хотел бы свернуть список, но мой аккумулятор - «Возможно».Однако функция, с которой я сворачиваюсь, принимает «извлеченное» значение из «Возможно», и, если один из них терпит неудачу, все они терпят неудачу.У меня есть решение, которое я нахожу глупым, но зная, как мало Хаскелла, как я, я считаю, что должен быть лучший путь.Скажем, у нас есть следующая проблема с игрушкой: мы хотим сложить список, но четверки по какой-то причине плохие, поэтому, если мы пытаемся суммировать по четырем в любое время, мы хотим вернуть Nothing.Мое текущее решение состоит в следующем:

import Maybe

explodingFourSum :: [Int] -> Maybe Int
explodingFourSum numberList =
    foldl explodingFourMonAdd (Just 0) numberList
    where explodingFourMonAdd =
        (\x y -> if isNothing x
                    then Nothing
                    else explodingFourAdd (fromJust x) y)

explodingFourAdd :: Int -> Int -> Maybe Int
explodingFourAdd _ 4 = Nothing
explodingFourAdd x y = Just(x + y)

Итак, есть ли способ очистить или устранить лямбду в explodingFourMonAdd, используя какую-то монадную складку?Или как-то каррирование в операторе >> =, чтобы сгиб работал как список функций, связанных >> =?

Ответы [ 5 ]

22 голосов
/ 19 сентября 2011

Я думаю, вы можете использовать foldM

explodingFourSum numberList = foldM explodingFourAdd 0 numberList

Это позволяет вам избавиться от дополнительной лямбды и , которая (Just 0) в начале.


Кстати, проверьте hoogle , чтобы найти функции, для которых вы не помните название.

6 голосов
/ 19 сентября 2011

Итак, есть ли способ очистить или устранить лямбду в explodingFourMonAdd, используя какую-то монадную складку?

Yapp.В Control.Monad есть функция foldM, которая именно то, что вы хотите здесь.Таким образом, вы можете заменить ваш звонок на foldl на foldM explodingFourAdd 0 numberList.

5 голосов
/ 19 сентября 2011

Вы можете использовать тот факт, что Maybe является монадой.Функция sequence :: [m a] -> m [a] имеет следующий эффект, если m равно Maybe: если для всех элементов в списке Just x для некоторого x, результатом является список всех этих значений.В противном случае результат будет Nothing.

Таким образом, вы сначала решаете для всех элементов, является ли это отказом.Например, возьмите ваш пример:

foursToNothing :: [Int] -> [Maybe Int]
foursToNothing = map go where
  go 4 = Nothing
  go x = Just x

Затем вы запустите последовательность и fmap сгиб:

explodingFourSum = fmap (foldl' (+) 0) . sequence . foursToNothing

Конечно, вы должны адаптировать это к вашему конкретному случаю.

3 голосов
/ 19 сентября 2011

Вот еще одна возможность, не упомянутая другими людьми. Вы можете отдельно проверить четверки и сделать сумму:

import Control.Monad
explodingFourSum xs = guard (all (/=4) xs) >> return (sum xs)

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

Конечно, есть хотя бы одна веская причина не , чтобы также использовать эту реализацию. Другие решения, упомянутые здесь, пересекают входной список только один раз; это хорошо взаимодействует с сборщиком мусора, позволяя в любой момент времени находиться в памяти только небольшим частям списка. Это решение, с другой стороны, дважды проходит xs, что не позволяет сборщику мусора собирать список во время первого прохода.

2 голосов
/ 19 сентября 2011

Вы также можете решить пример с игрушкой следующим образом:

import Data.Traversable

explodingFour 4 = Nothing 
explodingFour x = Just x

explodingFourSum = fmap sum . traverse explodingFour 

Конечно, это работает только потому, что одного значения достаточно, чтобы знать, когда расчет не удастся.Если условие сбоя зависит от обоих значений x и y в explodingFourSum, вам необходимо использовать foldM.

Кстати: причудливый способ написать explodingFour будет

import Control.Monad

explodingFour x = mfilter (/=4) (Just x)

Этот прием работает и для explodingFourAdd, но менее читабелен:

explodingFourAdd x y = Just (x+) `ap` mfilter (/=4) (Just y)
...