Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Plan B, or what's the opposite of Maybe's >>=?

Let's take two functions:

f :: a -> Maybe b
g :: b -> Maybe c

The function >>= would work in such a way that f >>= g would execute g with the result of f only if it's not Nothing. In other words, it requires both f and g to succeed to produce any result.

I'm implementing a parser and realized that my lexer would benefit from somewhat the opposite of this. That is:

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

which means try f and if it fails, try g as a backup plan. With a lexer it means try to match a token type with the current input and if it failed, try to match another token type (which would eventually be chained for all token types).

Searching Hoogle didn't result in any such function, but to me such a function seems to be useful in many places!

My question is thus, whether a variant of planb exists already that I should be using? If not, am I doing something extraordinary and there is a better way to achieve what I want?


P.S. I thought about whether such a function makes sense for Monads in general, but it doesn't make that much sense to me outside Maybe and perhaps a few others.

like image 751
Shahbaz Avatar asked Jul 23 '14 05:07

Shahbaz


2 Answers

The Alternative typeclass does precisely this, it is quite similar to MonadPlus but perhaps a bit more general.

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

Plugging this in to a simple test case:

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

Generates the expected result:

*Main> test
Just 3
like image 63
Nathan Howell Avatar answered Oct 23 '22 06:10

Nathan Howell


Note that your planb function really only needs to operate on Maybe values; calling the functions to produce them can be factored out.

planb :: Maybe a -> Maybe a -> Maybe a
planb Nothing b = b
planb a _ = a

And you'd call it like planb (f x) (g x) to get a Maybe result.

With that in mind, take a look at the MonadPlus class (as suggested by Franky in a comment):

planb = mplus

You might also be interested in msum, which takes a list of Maybe values and returns the first one (if any) that isn't Nothing. Here's a handy function:

matchSomehow :: [a -> Maybe b] -> a -> Maybe b
matchSomehow fs a = msum $ map ($a) fs
like image 22
Wyzard Avatar answered Oct 23 '22 06:10

Wyzard