4

私は現在、Haskell でいくつかのトランザクション メモリ ベンチマークに取り組んでおり、トランザクション内で乱数を利用できるようにしたいと考えています。現在、ここからランダムモナド/モナドトランスフォーマーを使用しています。次の例では、整数を含む 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

私の質問は、「これがこれを行うための最良の方法ですか?」ということだと思います。逆の方法、つまりランダムモナドを STM モナドに持ち上げる方がより自然で効率的であるように思われます。各トランザクションは多くの STM 操作を実行し、ランダム操作はほとんど実行しません。それぞれliftがある程度のオーバーヘッドを追加すると思います。だけにする方が効率的ではないでしょうかliftランダムな計算を行い、STM の計算はそのままにしておきますか? これを行っても安全ですか?STM モナド トランスフォーマーを定義すると、STM モナドで得られる優れた静的分離特性が損なわれるようです (つまり、IO を STM モナドに持ち上げることはできますが、トランザクションが中止された場合に IO アクションを元に戻すことについて心配する必要があります。問題数)。モナド変換子に関する私の知識はかなり限られています。トランスを使用する場合のパフォーマンスと相対的なオーバーヘッドに関する簡単な説明をいただければ幸いです。

4

1 に答える 1

2

STM は基本モナドです。もし があったとしたらatomically、これは現在STM a -> IO aどのように見えるべきか考えてみてくださいSTMT

あなたの特定の問題に対する解決策はほとんどありません。より簡単なのは、おそらくコードを再配置することです。

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本質的StateTには次のliftとおりです。

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

脱糖後の表記

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'

StateTそしてrunStateTキャンセルします:

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はこれをさらに減らすのに十分賢いので、状態は中間ペアを作成せずに通過するだけです(まだ確信が持てませんが、それを正当化するためにモナド法則を使用する必要があります):

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

それはあなたが得るものです

lift do x <- baseAction1
        y <- baseAction2
        return $ f x y
于 2015-07-11T09:13:18.417 に答える