所以,我一直在寻找可变哈希表(出于速度原因),Don Stewart 的更新答案让我尝试了hashtables。
由于对 ST Monad 有点缺乏经验,我成功地将给定的示例扩展为:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
import qualified Data.HashTable.ST.Cuckoo as C
import qualified Data.HashTable.Class as H
import Control.Monad.ST.Safe
import Data.Text
type HashTable s k v = C.HashTable s k v
foo :: ST s (HashTable s Text Int)
foo = do
ht <- H.newSized 1
H.insert ht "abc" 1
H.insert ht "dea" 3
return ht
add1 :: HashTable s Text Int -> ST s (HashTable s Text Int)
add1 ht = do
H.insert ht "abc" 2
H.insert ht "dea" 4
return ht
main = do
let x = runST $ H.toList =<< foo
print x
let y = runST $ H.toList =<< add1 =<< foo
print y
根据我的(有限的)理解,它允许以可变的方式对数据结构进行操作,但随后“冻结它们”并以可以逃脱的方式呈现结果runST
- 因此我们可以使用let
绑定,并且不是<-
。
但是,我没有看到如何在不总是将其转换为列表的情况下对哈希表进行操作。以下代码甚至无法编译:
-- does not compile
let z = runST foo
let w = runST $ add1 z
print w
它给出了以下错误(除其他外):hashtable.hs:31:19:
Couldn't match type `a' with `C.HashTable s Text Int'
`a' is a rigid type variable bound by
the inferred type of z :: a at hashtable01.hs:31:7
Expected type: ST s a
Actual type: ST s (HashTable s Text Int)
In the second argument of `($)', namely `foo'
In the expression: runST $ foo
In an equation for `z': z = runST $ foo
这可能是由于s
类型的限制,我的猜测是它可能正是出于这个原因。如果我们稍后使用z
它,它不会有相同的值,因为它add1
是在原地操作的,因此它不会是纯的。这个对吗?
但是,在这种特殊情况下,ST Monad 仅适用于以下情况:
- 你给一个固定的输入
- 您使用可变数据结构根据它计算新值
- 您冻结结果,使其再次不可变。
任何进一步的更改都需要一个 toList/fromList 有效地复制值并保持原始不可变。当我写这篇文章时,我在想 - 呃,这是一个纯函数的定义,在 haskell 中随处使用,因此是 ST Monad 的用例(我是否达到了 ST 的启蒙?)
所以,我想在这种情况下真正的问题是:这个 toList/fromList 操作是不是很昂贵(2xO(n)),难道没有其他方法可以在没有 toList/fromList 函数对的情况下对副本进行操作吗? . 还是我过于复杂了,我应该只使用 Hashtables 的 IO 版本?