Nice question! This is a little trickier than the usual “clean up a string of Maybe
s” in that you want short-circuiting on the Just
case, and continued computation on Nothing
. There is—of course—a function for that. Skip to the bottom for the answer, or read through for how you might find or derive it.
Finding the Answer
Hoogle. Pretty much always Hoogle. Think about what you've got and what you need, then start testing types against Hoogle. In this case you've got a sequence of Maybe a
values that you want to reduce to a single value. The key insight is that the sequence must all be of the same Maybe a
type, so it's possible (and thus probably necessary) to create a list (an actual [Maybe a]
) of them. Thus our candidate type signature—
? :: [Maybe a] -> Maybe a
Pop that type signature into Hoogle and the very first thing that comes up is—
-- "base" Control.Monad
-- | This generalizes the list-based concat function.
msum :: MonadPlus m => [m a] -> m a
Test it in GHCi and—
> msum [Nothing, Just 1, Just 2]
Just 1
> msum [Just 1, Nothing, Just 2]
Just 1
> msum [Nothing] :: Maybe Int
Nothing
Yep, that's what we want.
Deriving the Answer
There are probably two different ways you might come up with your own solution. Either start at the concrete and generalize, or cobble a solution from typeclasses down.
From the Concrete
Consider again the type of the function you need.
? :: [Maybe a] -> Maybe a
A no-frills solution to this with primitive recursion and pattern matching should be self-obvious (or will be with experience).
firstJust :: [Maybe a] -> Maybe a
firstJust [] = Nothing
firstJust (Nothing:ms) = firstJust ms
firstJust (j@(Just _):_) = j
From there we can deploy our usual bag of tricks higher-order functions to write this more idiomatically.
firstJust :: [Maybe a] -> Maybe a
firstJust = foldr orElse Nothing
where
orElse :: Maybe a -> Maybe a -> Maybe a
orElse Nothing m = m
orElse j@(Just _) _ = j
Now a few facts may percolate to the top of your brain and strike you as insight.
Maybe
encapsulates a notion of “failure”.
orElse
provides an alternative value in the case of failure.
Alternative
is a typeclass that exists.
So we get a spiffy one-liner—
firstJust :: [Maybe a] -> Maybe a
firstJust = foldr (<|>) Nothing
From the Typeclass Down
This all depends on which tools you're most familiar with. If you're a Monoid
fan and used to relying on newtypes you may recognize the pattern of condensing a list of values as Control.Monoid.mconcat
. Then you just need to write or choose an appropriate Monoid
instance, and of course if you're really on top of the ball you'll know that one already exists.
import Control.Monoid
import Data.Coerce (coerce) -- GHC 7.8.1
firstJust :: [Maybe a] -> Maybe a
firstJust = getFirst . mconcat . coerce
-- Compatible with older GHC but slower, `getFirst . mconcat . map First`
Just the Answer
This version relies on MonadPlus
, which ties up all of the other concepts we used and provides various handy functions to leverage their combined power. This version is as terse as it's gonna get.
import Control.Monad (msum) -- Or, Control.Applicative.asum
checkWon :: Board -> Maybe col
checkWon board = msum [ east moves col n
, west moves col n
, north moves col n
, south moves col n
]
where
moves = pieces board
n = target board
col = snd (moves !! 0)