План B, или что противоположность Maybe >> =?
Возьмем две функции:
f :: a -> Maybe b
g :: b -> Maybe c
Функция >>=
будет работать таким образом, что f >>= g
выполнит g
с результатом f
, только если это не Nothing
. Другими словами, для достижения любого результата требуется как f
, так и g
.
Я реализую парсер и понял, что мой лексер выиграет от некоторой противоположности этого. То есть:
f :: a -> Maybe b
g :: a -> Maybe b
planb :: (a -> Maybe b) -> (a -> Maybe b) -> (a -> Maybe b)
planb f g = \x -> case f x of
Nothing -> g x
res -> res
что означает try f
, и если он не работает, попробуйте g
в качестве плана резервного копирования. С помощью lexer это означает попытку сопоставить токен с текущим входом, и если он не сработал, попробуйте сопоставить другой тип токена (который в конечном итоге будет привязан для всех типов токенов).
Поиск Hoogle не привел к какой-либо такой функции, но мне такая функция кажется полезной во многих местах!
Мой вопрос, таким образом, существует ли вариант planb
, который я должен использовать? Если нет, делаю ли я что-то необыкновенное, и есть лучший способ добиться того, чего я хочу?
P.S. Я подумал о том, имеет ли смысл такая функция для Monad
вообще, но она не имеет для меня такого смысла вне Maybe
и, возможно, нескольких других.
Ответы
Ответ 1
Alternative
typeclass делает именно это, он очень похож на MonadPlus
, но, возможно, немного более общий.
import Control.Applicative
-- most general form
planb :: (Applicative g, Alternative f) => g (f a) -> g (f a) -> g (f a)
planb = liftA2 (<|>)
-- specialized to (->) and Maybe
planb' :: (a -> Maybe b) -> (a -> Maybe b) -> (a -> Maybe b)
planb' = planb
-- equivalent to planb' (and planb) but without the fancy combinators
planb'' :: (a -> Maybe b) -> (a -> Maybe b) -> a -> Maybe b
planb'' f g x = f x <|> g x
Вставьте это в простой тестовый пример:
test :: Maybe Int
test = do
a <- planb' (const Nothing) id (Just 1)
b <- planb' id id (Just 1)
c <- planb' id (const Nothing) (Just 1)
return $ a + b + c
Генерирует ожидаемый результат:
*Main> test
Just 3
Ответ 2
Обратите внимание, что ваша функция planb
действительно должна работать только с значениями Maybe
; вызов функций для их производства может быть учтен.
planb :: Maybe a -> Maybe a -> Maybe a
planb Nothing b = b
planb a _ = a
И вы бы назвали его как planb (f x) (g x)
, чтобы получить результат Maybe
.
С учетом этого взгляните на MonadPlus
класс (как предложено Franky в комментарии):
planb = mplus
Вас также может заинтересовать msum
, который берет список значений Maybe
и возвращает первый (если есть), который не является Nothing
. Здесь удобная функция:
matchSomehow :: [a -> Maybe b] -> a -> Maybe b
matchSomehow fs a = msum $ map ($a) fs