如何用foldM实现Haskell中State monad列表的累积操作?
First, we need to fix three key issues in your approach:
- Unify action types: All monadic actions in the list must share the same type (your original example mixes
State Stack ()andState Stack Int). - Handle failure gracefully: Replace hard errors with a
Maybetype to avoid crashing when an action fails (like popping from an empty stack). - Accumulate results correctly: Use a fold over the monadic actions to build a single accumulated result, continuing execution even if some actions return
Nothing.
Step 1: Adjust Actions to Return Maybe
Modify pop and push to return a consistent type that includes failure state:
import Control.Monad.State type Stack = [Int] -- Pop returns Maybe Int: Just x if successful, Nothing if stack is empty pop :: State Stack (Maybe Int) pop = do stack <- get case stack of (x:xs) -> do put xs return (Just x) [] -> return Nothing -- Push returns Maybe Int: Nothing (since it doesn't produce a value to accumulate) push :: Int -> State Stack (Maybe Int) push a = do stack <- get put (a:stack) return Nothing
Step 2: Accumulate Results with a Fold
Use foldl to iterate over the list of actions, accumulating results as you go. The fold builds a single State monad action that tracks the accumulated value:
-- Accumulate sum of successful pop results, ignoring failures and pushes stackManip :: State Stack Int stackManip = foldl step (return 0) [push 10, pop, push 20] where -- Step function: run the action, update accumulator step accAction action = do currentSum <- accAction result <- action case result of Just x -> return (currentSum + x) -- Add successful pop value Nothing -> return currentSum -- Keep sum unchanged for push or failure
Step 3: Test the Code
Run the action with an initial stack to verify the result:
main :: IO () main = do let initialStack = [200, 300] :: Stack (finalSum, finalStack) = runState stackManip initialStack putStrLn $ "Final Sum: " ++ show finalSum -- Output: Final Sum: 10 putStrLn $ "Final Stack: " ++ show finalStack -- Output: Final Stack: [20,200,300]
Handling Tuple Values
If your monads return tuples (as mentioned in your problem statement), adjust the accumulator and step function to handle tuple values. For example, if actions return Maybe (Int, String):
pop :: State Stack (Maybe (Int, String)) pop = do stack <- get case stack of (x:xs) -> do put xs return (Just (x, "Popped " ++ show x)) [] -> return Nothing push :: Int -> State Stack (Maybe (Int, String)) push a = do stack <- get put (a:stack) return (Just (0, "Pushed " ++ show a)) -- Accumulate sum of Ints and concatenate Strings stackManipTuple :: State Stack (Int, String) stackManipTuple = foldl step (return (0, "")) [push 10, pop, push 20] where step accAction action = do (sumAcc, strAcc) <- accAction result <- action case result of Just (x, s) -> return (sumAcc + x, strAcc ++ s ++ "; ") Nothing -> return (sumAcc, strAcc)
Why Not foldM?
Your original attempt uses foldM, but foldM expects a list of non-monadic values, not monadic actions. The foldl approach above is the correct way to iterate over a list of monadic actions and accumulate results in one pass.
内容的提问来源于stack exchange,提问作者Piskator

