You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用foldM实现Haskell中State monad列表的累积操作?

Solution

First, we need to fix three key issues in your approach:

  1. Unify action types: All monadic actions in the list must share the same type (your original example mixes State Stack () and State Stack Int).
  2. Handle failure gracefully: Replace hard errors with a Maybe type to avoid crashing when an action fails (like popping from an empty stack).
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 15:03:09