使用State monad的foldM无法通过类型检查,求排查
foldM结合State Monad的编译错误修复
问题描述
现有foldM相关资料未覆盖State Monad场景,官方文档无对应示例,编写的代码无法编译,代码及报错信息如下:
原代码
import Control.Monad.State.Lazy import Control.Monad ( foldM ) data Student = Student { name :: String, sid :: Int } deriving ( Show ) data TestResult = TestResult { tested :: Student, grade :: Int } deriving ( Show ) data Classroom = Classroom { students :: [ Student ] } deriving ( Show ) test :: Student -> State Classroom TestResult test s = do { c <- get; put c { students = s:(students c) }; return $ TestResult { tested = s, grade = 2 * (sid s) } } tests :: [ Student ] -> State Classroom [ TestResult ] tests = mapM test worst :: State Classroom TestResult worst = return $ TestResult { tested = Student { name = "MMM", sid = 0 }, grade = 0 } better :: TestResult -> TestResult -> State Classroom TestResult better r1 r2 = return $ case (grade r1) < (grade r2) of { True -> r2; False -> r1 } brightest :: [ Student ] -> State Classroom TestResult brightest students = foldM better worst (tests students) main = print $ evalState (brightest [ Student "X" 19, Student "Y" 49, Student "Z" 46 ]) (Classroom [])
编译报错
Main.hs:22:35: error: [GHC-83865] • Couldn't match type ‘StateT Classroom Data.Functor.Identity.Identity TestResult’ with ‘TestResult’ Expected: TestResult Actual: State Classroom TestResult • In the second argument of ‘foldM’, namely ‘worst’ In the expression: foldM better worst (tests students) In an equation for ‘brightest’: brightest students = foldM better worst (tests students) | 22 | brightest students = foldM better worst (tests students) | ^^^^^ Main.hs:22:42: error: [GHC-83865] • Couldn't match type ‘[TestResult]’ with ‘TestResult’ Expected: StateT Classroom Data.Functor.Identity.Identity TestResult Actual: State Classroom [TestResult] • In the third argument of ‘foldM’, namely ‘(tests students)’ In the expression: foldM better worst (tests students) In an equation for ‘brightest’: brightest students = foldM better worst (tests students) | 22 | brightest students = foldM better worst (tests students) | ^^^^^^^^^^^^^^
错误分析
两个错误均源于对foldM类型签名的误解:
foldM的类型为Foldable t => (b -> a -> m b) -> b -> t a -> m b,其中:- 第二个参数是累积器的初始值,类型为
b,而非m b。原代码中worst被包裹在Statemonad中,导致类型不匹配。 - 第三个参数是待遍历的序列
t a,而非m (t a)。原代码中tests students返回的是State Classroom [TestResult],是monadic容器中的列表,无法直接作为foldM的输入。
- 第二个参数是累积器的初始值,类型为
修复方案
- 将
worst改为纯TestResult值,不需要包裹在State中; - 在
brightest函数中,先通过do块提取tests students返回的测试结果列表,再传给foldM; - 简化代码中的冗余语法,提升可读性。
修复后的完整代码
import Control.Monad.State.Lazy import Control.Monad ( foldM ) data Student = Student { name :: String, sid :: Int } deriving ( Show ) data TestResult = TestResult { tested :: Student, grade :: Int } deriving ( Show ) data Classroom = Classroom { students :: [ Student ] } deriving ( Show ) test :: Student -> State Classroom TestResult test s = do c <- get put c { students = s : students c } return $ TestResult s (2 * sid s) tests :: [ Student ] -> State Classroom [ TestResult ] tests = mapM test worst :: TestResult worst = TestResult (Student "MMM" 0) 0 better :: TestResult -> TestResult -> State Classroom TestResult better r1 r2 = return $ if grade r1 < grade r2 then r2 else r1 brightest :: [ Student ] -> State Classroom TestResult brightest students = do allResults <- tests students foldM better worst allResults main = print $ evalState (brightest [ Student "X" 19, Student "Y" 49, Student "Z" 46 ]) (Classroom [])
运行结果
执行后会输出最高成绩的测试结果:
TestResult {tested = Student {name = "Y", sid = 49}, grade = 98}
同时Classroom的students列表会包含所有测试过的学生(test函数会将学生添加到列表中)。
内容的提问来源于stack exchange,提问作者OrenIshShalom
相关产品推荐
相关产品推荐

