Добавление генерации случайных чисел в монаду STM в Haskell
В настоящее время я работаю над некоторыми тестами транзакционной памяти в Haskell и хотел бы иметь возможность использовать случайные числа в транзакции. Я в настоящее время использую Random монаду / монадный преобразователь отсюда. В следующем примере у меня есть массив TVars, содержащий целые числа и транзакцию, которая случайным образом выбирает 10 твар в массиве для увеличения, например:
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, если транзакция прерывается, что представляет количество вопросов). Мои знания о монадных трансформаторах довольно ограничены. Краткое объяснение относительно производительности и относительных накладных расходов на использование трансформатора будет высоко ценится.
1 ответ
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
который после desugaring сделать запись
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