Хороший способ отслеживать несколько ссылок между функциями в монаде ST?

Я пишу код (сэмплер MCMC Metropolis-Hastings), который будет использовать генератор случайных чисел и на его основе изменять массив и, возможно, другие структуры.

Моя первоначальная идея состояла в том, чтобы использовать монаду ST, чтобы я мог использовать массивы ST и пакет mersenne-random-pure64, сохраняя генератор PureMT как часть состояния.

Однако я хочу иметь возможность разделить часть работы на отдельные вспомогательные функции (например, для выборки случайного целого числа в заданном диапазоне, для обновления структуры массива и, возможно, более сложных вещей). Для этого, я думаю, мне нужно было бы передать ссылки на генерацию PureMT и массив всем функциям, что может быстро стать очень уродливым, если мне нужно сохранить больше состояний.

Мой инстинкт состоит в том, чтобы сгруппировать все состояние в один тип данных, к которому я могу получить доступ где угодно, как я бы использовал монаду состояния, определяя новый тип данных, но я не знаю, возможно ли это с монадой ST или правильный способ сделать это.

Есть ли какие-нибудь хорошие шаблоны для подобных вещей? Я хочу, чтобы вещи были как можно более общими, потому что мне, вероятно, нужно будет добавить дополнительное состояние и построить более монадический код вокруг существующих частей.

Я попытался найти примеры кода монады ST, но, похоже, он не охвачен в Real World Haskell, а примеры вики haskell очень короткие и простые.

Благодарность!


person Tom    schedule 10.08.2011    source источник


Ответы (2)


Мой инстинкт состоит в том, чтобы сгруппировать все состояние в один тип данных, к которому я могу получить доступ где угодно, как я бы использовал монаду состояния, определяя новый тип данных, но я не знаю, возможно ли это с монадой ST или правильный способ сделать это.

Есть ли какие-нибудь хорошие шаблоны для подобных вещей? Я хочу, чтобы вещи были как можно более общими, потому что мне, вероятно, нужно будет добавить дополнительное состояние и построить более монадический код вокруг существующих частей.

Здесь важно понять, что совершенно неважно, что вы используете ST. Сами ST ссылки - это просто обычные значения, к которым вам нужен доступ в разных местах, но вы не на самом деле хотите их изменять! Изменчивость происходит в ST, но значения STRef и многое другое в основном доступны только для чтения. Это имена, указывающие на изменяемые данные.

Конечно, монада Reader - это доступ только для чтения к окружающей среде. Уродливая передача ссылок на все функции - это именно то, что она делает для вас, но поскольку вы уже находитесь в ST, вы можете просто закрепить это как преобразователь монад. В качестве простого примера вы можете сделать что-то вроде этого:

newtype STEnv s e a = STEnv (ReaderT e (ST s) a)
    deriving (Functor, Applicative, Monad)

runEnv :: STEnv s e a -> ST s e -> ST s a
runEnv (STEnv r) e = runReaderT r =<< e

readSTEnv :: (e -> STRef s a) -> STEnv s e a
readSTEnv f = STEnv $ lift . readSTRef . f =<< ask

writeSTEnv :: (e -> STRef s a) -> a -> STEnv s e ()
writeSTEnv f x = STEnv $ lift . flip writeSTRef x . f =<< ask

Для большей общности вы можете абстрагироваться от деталей ссылочных типов и превратить его в общую «среду с изменяемыми ссылками. "монада.

person C. A. McCann    schedule 10.08.2011
comment
Это звучит как хорошая идея, я забыл, что ссылки на самом деле не меняются. Спасибо за это! Мне также интересно, возможно ли использовать монаду ST внутри себя для разделения независимых вычислений с отслеживанием состояния, выполняемых в среде более высокого уровня, к которой им не нужен доступ, с их собственным скрытым локальным состоянием. Нужно ли мне использовать для этого преобразователь монады ST, или вы можете просто использовать let a = runST $ .... внутри монады ST? Если вы можете (без использования преобразователя), что произойдет, если вы попытаетесь разыменовать ссылку из включающей монады (при условии, что они будут в области видимости)? - person Tom; 11.08.2011
comment
@Tom: Для _1 _-- нет преобразователя, как для IO, он всегда внизу любого стека. Более того, любые два полных ST вычисления - то есть вещи, происходящие внутри вызова _4 _ - полностью изолированы от состояния друг друга, и любая попытка их смешать приведет к ошибке типа. Вы можете передавать непрозрачные значения, как вы могли бы передавать IORef внутри ST, но вы также не можете использовать их больше, чем могли бы использовать IORefs. Однако вы можете использовать runST как обычно; Результатом этого, как всегда, является чистая ценность. - person C. A. McCann; 11.08.2011
comment
@Tom: Кроме того, если вы используете ReaderT, как в моем примере, вы, вероятно, могли бы без особых проблем реализовать несколько видов операций брекетинга - клонирование текущей среды для запуска в отдельном внутреннем ST, выполнение чего-то в том же ST с другая среда и т. д. - person C. A. McCann; 11.08.2011
comment
спасибо, хотя мне кажется это трансформер для ST - hackage.haskell.org/packages/archive/STMonadTrans/0.2/doc/html/, но я не думаю, что это необходимо для того, что я имел в виду. - person Tom; 11.08.2011
comment
@Tom: Заметьте, что там преобразователь взаимодействует с внутренним устройством ST таким образом, что ломается при объединении с определенными другими монадами, поэтому я не уверен, что это действительно имеет значение. Но в любом случае да, потому что то, что вы делаете, вероятно, лучше всего. Преобразователь будет полезен только в том случае, если вам нужно применить управляющие структуры другой монады к внутреннему токену состояния, что чревато опасностями и в любом случае не очень полезно. - person C. A. McCann; 11.08.2011

Вы можете использовать монаду ST так же, как монаду ввода-вывода, имея в виду, что вы получаете только массивы и ссылки, а не другие полезности ввода-вывода. Так же, как IO, вы можете наложить на него StateT, если хотите прозрачно передать какое-то состояние через свои вычисления.

person sclv    schedule 10.08.2011
comment
О, я не думал об этом, думаю, это решит мои проблемы. - person Tom; 10.08.2011