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

使用ListT (State Int)未实现惰性求值,如何在首个匹配后终止遍历?

ListT + State 惰性遍历未提前终止的问题与解决

我用ListT (State Int) a实现了一个带访问次数上限的非确定性搜索空间遍历,原本以为对结果用take 1取首个解后,程序会停止后续的树遍历——毕竟Control.Monad.Trans.State是惰性的,而且我用的list-t是官方宣称的「正确列表monad转换器,适用于基础流处理」。但测试结果却不符合预期:

import qualified Data.List as L
import Data.Maybe
import Control.Monad
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import qualified ListT as LT
import Debug.Trace

dictionary = ["aabb","aa","bb"]

parse :: String -> [[String]]
parse = take 1 . flip evalState 0 . LT.toList . go [] where
  go ::  [String] -> String -> LT.ListT (State Int) [String]
  go ac "" = trace ("trace: " ++ show ac) (return ac)
  go ac input = do
    callCount <- lift get
    if callCount >= 10 then trace "overflow" mzero else do
      nextWord <- LT.fromFoldable $ filter (`L.isPrefixOf` input) dictionary
      let rest = fromJust $ L.stripPrefix nextWord input
      lift $ modify (+1)
      go (nextWord:ac) rest

测试输出:

ghci> parse "aabb"
trace: ["aabb"]
trace: ["bb","aa"]   -- 为什么会出现这一行?
[["aabb"]]

我忽略了什么?怎么利用惰性在找到首个匹配后就退出?另外我不想用这种重置状态的 workaround:

go ac "" = do
  lift $ modify (const 10)
  return ac

问题根源:调用顺序搞反了

你当前的写法是先把整个ListT转换成完整列表,再取第一个元素:
take 1 . flip evalState 0 . LT.toList

这里的关键是:LT.toList会遍历ListT的所有分支,执行每个分支对应的State计算,把所有结果收集成列表后,才会执行take 1。也就是说,哪怕你只需要第一个解,所有搜索分支的计算还是会被完整执行,这就导致了第二个trace输出。

list-t的惰性特性,需要你在ListT monad内部提前终止,而不是等所有结果生成后再截断。

解决方法:在ListT层面取首个元素

调整调用顺序,用LT.take 1在ListT转换器内部只保留第一个结果,再执行State计算:

修改后的parse函数:

parse :: String -> [[String]]
parse input = flip evalState 0 . LT.toList $ LT.take 1 (go [] input)

测试输出就会符合预期:

ghci> parse "aabb"
trace: ["aabb"]
[["aabb"]]

原理说明

LT.take 1会让ListT在生成第一个结果后,立即停止遍历后续的分支。此时LT.toList只会执行第一个分支的State计算,不会触发第二个分支(即匹配"aa"的分支),自然就不会出现多余的trace输出。

State的惰性在这里没有问题,问题出在你没有利用list-t的流处理特性提前截断,而是等所有分支执行完再做截断。

内容的提问来源于stack exchange,提问作者cobra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 12:45:23