I want to break a loop in a situation like this:
import Data.Maybe (fromJust, isJust, Maybe(Just))
tryCombination :: Int -> Int -> Maybe String
tryCombination x y
| x * y == 20 = Just "Okay"
| otherwise = Nothing
result :: [String]
result = map (fromJust) $
filter (isJust) [tryCombination x y | x <- [1..5], y <- [1..5]]
main = putStrLn $ unlines $result
Imagine, that "tryCombination" is a lot more complicated like in this example. And it's consuming a lot of cpu power. And it's not a evalutation of 25 possibilities, but 26^3.
So when "tryCombination" finds a solution for a given combination, it returns a Just, otherwise a Nothing. How can I break the loop instantly on the first found solution?
To answer your question, find function is what you need. After you get
Maybe (Maybe String)
you can transform it intoMaybe String
withjoin
While
find
is nicer, more readable and surely does only what's needed, I wouldn't be so sure about inefficiency of the code that you have in a question. The lazy evaluation would probably take care of that and compute only what's needed, (extra memory can still be consumed). If you are interested, try to benchmark.Laziness can actually take care of that in this situation.
By calling
unlines
you are requesting all of the output of your "loop"1, so obviously it can't stop after the first successfultryCombination
. But if you only need one match, just uselistToMaybe
(fromData.Maybe
); it will convert your list toNothing
if there are no matches at all, orJust
the first match found.Laziness means that the results in the list will only be evaluated on demand; if you never demand any more elements of the list, the computations necessary to produce them (or even see whether there are any more elements in the list) will never be run!
This means you often don't have to "break loops" the way you do in imperative languages. You can write the full "loop" as a list generator, and the consumer(s) can decide independently how much of the they want. The extreme case of this idea is that Haskell is perfectly happy to generate and even filter infinite lists; it will only run the generation code just enough to produce exactly as many elements as you later end up examining.
1 Actually even
unlines
produces a lazy string, so if you e.g. only read the first line of the resulting joined string you could still "break the loop" early! But you print the whole thing here.The evaluation strategy you are looking for is exactly the purpose of the
Maybe
instance ofMonadPlus
. In particular, there is the functionmsum
whose type specializes in this case toIntuitively, this version of
msum
takes a list of potentially failing computations, executes them one after another until the first computations succeeds and returns the according result. So,result
would becomeOn top of that, you could make your code in some sense agnostic to the exact evaluation strategy by generalizing from
Maybe
to any instance ofMonadPlus
:To get your desired behavior, just run the following:
However, if you decide you need not only the first combination but all of them, just use the
[]
instance ofMonadPlus
:I hope this helps more on a conceptual level than just providing a solution.
PS: I just noticed that
MonadPlus
andmsum
are indeed a bit too restrictive for this purpose,Alternative
andasum
would have been enough.Simple solution:
find
andjoin
It looks like you're looking for
Data.List.find
.find
has the type signatureSo you'd do something like
Or, if you don't want a
Maybe (Maybe String)
(why would you?), you can fold them together withControl.Monad.join
, which has the signatureso that you have
More advanced solution:
asum
If you wanted a slightly more advanced solution, you could use
Data.Foldable.asum
, which has the signatureWhat it does is pick out the first
Just
value from a list of many. It does this by using theAlternative
instance ofMaybe
. TheAlternative
instance ofMaybe
works like this: (importControl.Applicative
to get access to the<|>
operator)In other words, it picks the first
Just
value from two alternatives. Imagine putting<|>
between every element of your list, so thatgets turned to
This is exactly what the
asum
function does! Since<|>
is short-circuiting, it will only evaluate up to the firstJust
value. With that, your function would be as simple asWhy would you want this more advanced solution? Not only is it shorter; once you know the idiom (i.e. when you are familiar with
Alternative
andasum
) it is much more clear what the function does, just by reading the first few characters of the code.