在 Haskell 中将随机数生成添加到 STM monad

Adding random number generation to the STM monad in Haskell

我目前正在 Haskell 中进行一些事务性内存基准测试,并希望能够在事务中使用随机数。我目前正在使用来自 here 的随机 monad/monad 转换器。在下面的示例中,我有一个包含整数的 TVar 数组和一个在数组中随机选择 10 个 tvar 进行递增的事务,例如:

tvars :: STM (TArray Int Int)
tvars = newArray (0, numTVars) 0

write :: Int -> RandT StdGen STM [Int]
write 0 = return []
write i = do
    tvars <- lift tvars
    rn <- getRandomR (0, numTVars)
    temp <- lift $ readArray tvars rn
    lift $ writeArray tvars rn (temp + 1)
    rands <- write (i-1)
    lift $ return $ rn : rands

我想我的问题是 "Is this the best way to go about doing this?" 似乎更 natural/efficient 反过来,即将随机 monad 提升到 STM monad。每笔交易做大量的 STM 操作,很少有随机操作。我假设每个 lift 都会增加一些开销。仅 lift 随机计算而单独留下 STM 计算不是更有效吗?这样做安全吗?似乎定义一个 STM monad 转换器会破坏我们通过 STM monad 获得的良好的静态分离属性(即我们可以将 IO 提升到 STM monad,但是如果事务中止呈现题数)。我对 monad 转换器的了解非常有限。非常感谢关于使用变压器的性能和相对开销的简要说明。

STM 是一个基础 monad,如果我们有 STMT.

,想想 atomically,目前 STM a -> IO a 应该是什么样子

对于您的特定问题,我几乎没有什么解决方案。比较简单的大概是重新整理一下代码:

write :: Int -> RandT StdGen STM [Int]
write n = do
   -- random list of indexes, so you don't need to interleave random and stm code at all
   rn <- getRandomRs (0, numTVars) 
   lift $ go rn
   where go []     = return []
         go (i:is) = do tvars <- tvars -- this is redundant, could be taken out of the loop
                        temp <-  readArray tvars i
                        writeArray tvars i (temp + 1)
                        rands <- go is
                        return $ i : rands

然而 RandT 本质上是 StateTlift:

instance MonadTrans (StateT s) where
    lift m = StateT $ \ s -> do
        a <- m
        return (a, s)

所以表格的代码:

do x <- lift baseAction1
   y <- lift baseAction2
   return $ f x y

将会

do x <- StateT $ \s -> do { a <- baseAction1; return (a, s) }
   y <- StateT $ \s -> do { a <- baseAction2; return (a, s) }
   return $ f x y

这是脱糖后的do符号

StateT (\s -> do { a <- baseAction1; return (a, s) }) >>= \ x ->
StateT (\s -> do { a <- baseAction2; return (a, s) }) >>= \ y ->
return $ f x y

先内联>>=

StateT $ \s -> do
  ~(a, s') <- runStateT (StateT (\s -> do { a <- baseAction1; return (a, s) })) s
  runStateT ((\ x -> StateT (\s -> do { a <- baseAction2; return (a, s) }) >>= \ y -> return $ f x y) a) s'

StateTrunStateT抵消:

StateT $ \s -> do
  ~(x, s') <- do { a <- baseAction1; return (a, s) }))
  runStateT ((\ x -> StateT (\s -> do { a <- baseAction2; return (a, s) }) >>= \ y -> return $ f x y) x) s'

经过几个内联/缩减步骤后:

StateT $ \s -> do
  ~(x, s') <- do { a <- baseAction1; return (a, s) }))
  ~(y, s'') <- do { a <- baseAction2; return (a, s') }))
  return (f x y, s'')

可能 GHC 足够聪明,可以进一步减少这种情况,所以状态只是通过而不创建中间对(但我不确定,应该使用 monad 法则来证明这一点):

StateT $ \s -> do
   x <- baseAction1
   y <- baseAction2
   return (f x y, s)

这是你从中得到的

lift do x <- baseAction1
        y <- baseAction2
        return $ f x y