Haskell中ST monad下HashTable与纯函数结合及遍历问题
使用ST Monad的HashTable实现词频统计
你的核心问题是**foldl是纯函数的折叠工具,无法直接处理ST Monad中的动作**——H.mutate返回的是ST s a类型的单子动作,而非纯值,自然不能作为foldl的累加器传递。要在ST语境下批量执行修改操作,需要用Monadic版本的遍历/折叠函数,比如mapM_或者foldM(均来自Control.Monad)。
关键修正点
- 用
mapM_替代foldl:mapM_专门用于遍历列表并依次执行每个Monadic动作,不需要返回累加值,刚好适配你修改可变哈希表的场景。 - 简化
mutate的处理逻辑:用maybe 1 (+1) v替代case表达式,更简洁地处理计数更新。 - 将ST语境中的哈希表转为纯值:ST中的可变结构无法直接导出,需用
C.toList将其转换为纯的键值对列表,再通过runST提取结果。
修正后的完整代码
{-# LANGUAGE TupleSections #-} import Data.Char(toLower) import Data.Hashable(Hashable) import Text.Regex.TDFA import Control.Monad (mapM_, void) import Control.Monad.ST import qualified Data.HashTable.ST.Basic as H import qualified Data.HashTable.Class as C type HashTable s k v = H.HashTable s k v st = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. " re = "[a-z0-9]+" count :: (Hashable k, Eq k) => [k] -> ST s (HashTable s k Int) count ws = do ht <- H.new -- 遍历所有单词,更新哈希表计数 mapM_ (\w -> H.mutate ht w $ \v -> (Just $ maybe 1 (+1) v, ())) ws return ht main = do let wordsList = getAllTextMatches ((map toLower st) =~ re) :: [String] -- 运行ST动作,将哈希表转为纯键值对列表 freqList = runST $ count wordsList >>= C.toList putStrLn $ show freqList
额外说明
H.mutate的第二个返回值可以是任意类型(这里用()表示不需要返回数据),它会被mapM_忽略。- 如果坚持用
foldM,逻辑类似:
但count ws = do ht <- H.new void $ foldM (\ht w -> do void $ H.mutate ht w $ \v -> (Just $ maybe 1 (+1) v, ()) return ht) ht ws return htmapM_更符合这个场景的语义,因为我们不需要维护累加器的状态,只需要执行一系列修改动作。
内容的提问来源于stack exchange,提问作者Logrus
相关产品推荐
相关产品推荐

