Haskell中Monad上下文的return机制及RegModule代码疑问
1. Haskell中Monad上下文内return的工作原理
return是Haskell Monad类型类的核心方法,类型签名为:
return :: Monad m => a -> m a
它的核心作用是将纯值包装进Monad的上下文环境,让纯值能融入Monad的链式计算流程(通过>>=操作符)。不同Monad对return的实现各有侧重:
MaybeMonad中,return x = Just x,把值包装为成功的上下文;- 列表Monad中,
return x = [x],代表唯一的计算结果分支; IOMonad中,return x是一个无IO操作、直接返回x的动作。
本质上,return是Monad的“纯值入口”,它不触发任何Monad特有的逻辑,只是完成纯值到Monad上下文的转换。
2. RegModule Monad实例与scanChar的细节疑问
先回顾RegModule的定义:
newtype RegModule d a = RegModule {runRegModule :: String -> Int -> d -> [(a, Int, d)]}
它本质是一个封装的函数,输入字符串、当前位置、状态,输出三元组列表——每个三元组对应“计算结果、位置偏移、新状态”,列表则表示非确定性计算的多个可能分支。
为什么scanChar第一个分支用return (c, i+1, d)而非[(c, i+1, d)]?
看RegModule的Monad实例中return的实现:
return a = RegModule (\_s _i d -> return (a, 0, d))
这里存在两层return:
- 外层是RegModule Monad的
return,负责把纯值a包装成RegModule类型; - 内层是列表Monad的
return,等价于\x -> [x],把三元组包装成单元素列表。
在scanChar的定义里,RegModule构造器要求传入的函数返回[(a, Int, d)]类型。这里的return (c, i+1, d)就是列表Monad的return,和[(c, i+1, d)]效果完全一致。之所以用return,是为了贴合Monad的抽象风格——明确表示这是一个单分支的成功结果,和代码中其他Monad风格的写法保持一致,而非直接写列表字面量。
为什么第二个分支无需写return []?
列表Monad中,空列表[]本身就代表“无计算结果”(失败分支)。如果写return [],会把空列表包装成单元素列表[[]],其类型为[[(a, Int, d)]],和我们需要的[(a, Int, d)]类型完全不匹配,会触发编译错误。
在scanChar的空输入分支中,我们需要表达“没有可匹配的字符,因此无结果”,直接返回[]正好符合语义,也满足类型要求,完全不需要额外用return包装。
完整示例代码
import qualified Data.Set as S import Control.Monad type CharSet = S.Set Char data RE = RClass Bool CharSet newtype RegModule d a = RegModule {runRegModule :: String -> Int -> d -> [(a, Int, d)]} instance Monad (RegModule d) where return a = RegModule (\_s _i d -> return (a, 0, d)) m >>= f = RegModule (\s i d -> do (a, j, d') <- runRegModule m s i d (b, j', d'') <- runRegModule (f a) s (i + j) d' return (b, j + j', d'')) instance Functor (RegModule d) where fmap = liftM instance Applicative (RegModule d) where pure = return; (<*>) = ap scanChar :: RegModule d Char scanChar = RegModule (\s i d -> case drop i s of (c:cs) -> return (c, i+1, d) [] -> [] ) regfail :: RegModule d a regfail = RegModule (\_s _i d -> []) regEX :: RE -> RegModule [String] () regEX (RClass b cs) = do next <- scanChar if (S.member next cs) then return () else regfail runRegModuleThrice :: RegModule d a -> String -> Int -> d -> [(a, Int, d)] runRegModuleThrice matcher input startPos state = let (result1, pos1, newState1) = head $ runRegModule matcher input startPos state (result2, pos2, newState2) = head $ runRegModule matcher input pos1 newState1 (result3, pos3, newState3) = head $ runRegModule matcher input pos2 newState2 in [(result1, pos1, newState1), (result2, pos2, newState2), (result3, pos3, newState3)]
内容的提问来源于stack exchange,提问作者Piskator

