我正在努力在 Haskell 中实现UCT算法,这需要大量的数据处理。无需过多详细介绍,它是一种模拟算法,在每个“步骤”中,根据一些统计属性选择搜索树中的一个叶节点,在该叶上构造一个新的子节点,并对应于新叶子及其所有祖先都已更新。
考虑到所有这些杂耍,我还不够敏锐,无法弄清楚如何使整个搜索树成为一个不错的不可变数据结构 à la Okasaki。相反,我一直在玩弄ST
monad,创建由 mutable 组成的结构STRef
。一个人为的例子(与 UCT 无关):
import Control.Monad
import Control.Monad.ST
import Data.STRef
data STRefPair s a b = STRefPair { left :: STRef s a, right :: STRef s b }
mkStRefPair :: a -> b -> ST s (STRefPair s a b)
mkStRefPair a b = do
a' <- newSTRef a
b' <- newSTRef b
return $ STRefPair a' b'
derp :: (Num a, Num b) => STRefPair s a b -> ST s ()
derp p = do
modifySTRef (left p) (\x -> x + 1)
modifySTRef (right p) (\x -> x - 1)
herp :: (Num a, Num b) => (a, b)
herp = runST $ do
p <- mkStRefPair 0 0
replicateM_ 10 $ derp p
a <- readSTRef $ left p
b <- readSTRef $ right p
return (a, b)
main = print herp -- should print (10, -10)
显然,如果不使用 ,这个特定的示例会更容易编写ST
,但希望很清楚我将在哪里使用这个......如果我要将这种风格应用于我的 UCT 用例,那是不是走错路了?
几年前有人在这里问过类似的问题,但我认为我的问题有点不同......我在适当的时候使用 monad 封装可变状态没有问题,但正是“适当的时候”子句让我着迷。我担心我会过早地回到面向对象的心态,我有一堆带有 getter 和 setter 的对象。不完全是惯用的 Haskell ......
另一方面,如果它对于某些问题来说是一种合理的编码风格,我想我的问题就变成了:是否有任何众所周知的方法来保持这种代码的可读性和可维护性?我对所有显式的读取和写入感到有点恶心,尤其是不得不从monadSTRef
内部的基于 my 的结构转换为ST
外部的同构但不可变的结构。