问题
Original question
LYAH, in For a Few Monads More shows this function,
solveRPN :: String -> Maybe Double
solveRPN st = do
[result] <- foldM foldingFunction [] (words st)
return result
which uses pattern-matching in conjunction with do
expression to esure that the monad coming out of foldM
wraps a singleton list.
In order to truly understand the nature of the do
expression as well as of Monad
, I have been rewriting most example from that book using >>=
and >>
instead of the do
expression, a practice which is suggested somewhere in Real World Haskell too, but I don't remember which chapter.
As regards the function above, I'm a bit puzzled. What is the most concise way to write it without using the do
expression? The best I could come up with is
solveRPN :: String -> Maybe Double
solveRPN s = foldM step [] (words s) >>= \x -> case x of
[y] -> Just y
_ -> Nothing
but I was hoping in something cleaner, as this one is pretty noisy for 2 reasons:
- it uses a lambda
- it uses the
case
expression, which doesn't look much nicer than aif
-then
-else
.
This question is related to the present one.
I asked another question which actually highlights the basic issue in this one:
How do I pull the head
out of a list and succeed only for singleton lists?
And this has nothing to do with the Maybe
that wraps the result in solveRPN
.
Update
The answer I accepted there proposes a clear solution the question above:
func :: [a] -> a
func = foldr1 (const (const undefined))
which can be used to write easily solveRPN
in pointfree style:
solveRPN :: String -> Maybe Double
solveRPN st = foldM foldingFunction [] (words st) >>= Just . foldr1 (const (const undefined))
However, this absolutely unsatisfying, as it does not fully take advantage of the Maybe
monad, failing at runtime instead of returning Nothing
when the output is incorrect.
I think that playing around with that could lead me or someone else to answer my original question without do
/case
/helpers.
回答1:
The desugaring of do notation specified in the Haskell Report actually includes a pattern match in order to handle pattern match failure with fail
, now specified in the MonadFail
typeclass. It may be written as a case
or as a function.
do {e} = e do {e;stmts} = e >> do {stmts} do {p <- e; stmts} = let ok p = do {stmts} ok _ = fail "..." in e >>= ok do {let decls; stmts} = let decls in do {stmts}
So in your example, that could look like this.
solveRPN :: String -> Maybe Double
solveRPN st = foldM foldingFunction [] (words st) >>= \ x -> case x of
[result] -> return result
_ -> fail "Pattern match failure in do expression at file:line:col"
(And of course fail message :: Maybe a
is just Nothing
.)
You can make this slightly more concise with LambdaCase
to avoid the extra variable.
{-# LANGUAGE LambdaCase #-}
solveRPN :: String -> Maybe Double
solveRPN st = foldM foldingFunction [] (words st) >>= \ case
[result] -> return result
_ -> fail "Pattern match failure in do expression at file:line:col"
That’s the standard desugaring, but if you want to golf this further, you can use a helper function. The standard listToMaybe :: [a] -> Maybe a
works if you want to allow non-singleton lists.
import Data.Maybe (listToMaybe)
solveRPN :: String -> Maybe Double
solveRPN st = foldM foldingFunction [] (words st) >>= listToMaybe
There is no standard singletonToMaybe
but you can easily write one.
singletonToMaybe :: [a] -> Maybe a
singletonToMaybe [x] = Just x
singletonToMaybe _ = Nothing
-- or:
singletonToMaybe xs = guard (isSingleton xs) *> listToMaybe xs
isSingleton = null . drop 1
Then write the function in point-free style using the monadic composition operators <=<
or >=>
.
solveRPN :: String -> Maybe Double
solveRPN = singletonToMaybe <=< foldM foldingFunction [] . words
来源:https://stackoverflow.com/questions/62251843/changing-a-do-expression-that-uses-pattern-matching-to-application-of-the-bind-o