Неявное выполнение STRef в среде во время вычислений
Я работаю над некоторыми большими вычислениями, которые требуют использования изменяемых данных в некоторые критические моменты. Я хочу как можно больше избегать IO. Моя модель раньше состояла из ExceptT
над ReaderT
над State
тип данных, и теперь я хочу заменить State
с упомянутыми ST
,
Для упрощения, давайте предположим, что я хотел бы сохранить один STRef
с Int
в течение всего вычисления, и давайте пропустить ExceptT
внешний слой. Моей первоначальной идеей было поставить STRef s Int
в ReaderT
среда:
{-#LANGUAGE Rank2Types#-}
{-#LANGUAGE ExistentialQuantification#-}
data Env = Env { supply :: forall s. STRef s Int }
data Comp a = forall s. Comp (ReaderT Env (ST s) a)
И оценщик:
runComp (Comp c) = runST $ do
s <- newSTRef 0
runReaderT c (Env {supply = s}) -- this is of type `ST s a`
... и это не удается, потому что
Не удалось сопоставить тип 's' с 's1'
Что кажется ясным, потому что я смешал два отдельных фантомных состояния ST. Однако я понятия не имею, как это обойти. Я пытался добавить фантом s
как Comp
а также Env
параметр, но результат был таким же, и код стал более уродливым (но менее подозрительным из-за отсутствия этих forall
с).
Особенность, которую я пытаюсь достичь здесь, состоит в том, чтобы сделать supply
доступны в любое время, но не переданы явно (это не заслуживает этого). Самое удобное место для его хранения - это окружение, но я не вижу способа его инициализации.
Я знаю, что есть такая вещь, как STT
монадный преобразователь, который может помочь в этом, но он не совместим с более амбициозными структурами данных, такими как хеш-таблицы (или это так?), поэтому я не хочу использовать его, если не могу свободно использовать классический ST
библиотеки там.
Как правильно оформить эту модель? Под "правильно" я подразумеваю не только "проверку типов", но и "быть вежливым с остальным кодом" и "максимально гибко".
1 ответ
runST
нужно дать полиморфный аргумент, и вы хотите, чтобы ваш аргумент пришел из Comp
, следовательно Comp
должен содержать полиморфную вещь.
newtype Env s = Env { supply :: STRef s Int }
newtype Comp a = Comp (forall s. ReaderT (Env s) (ST s) a)
runComp (Comp c) = runST $ do
s <- newSTRef 0
runReaderT c (Env s)
Так как Comp
закрывается s
, вы не можете выполнить действие, которое возвращает содержимое STRef
; но вы можете выставить действие, которое использует ссылку внутри:
onRef :: (forall s. STRef s Int -> ST s a) -> Comp a
onRef f = Comp $ asks supply >>= lift . f
например onRef readSTRef :: Comp Int
а также onRef (`modifySTRef` succ) :: Comp ()
, Другой выбор, который может быть более эргономичным, это сделать Comp
сам мономорфный, но есть runComp
требуют полиморфного действия. Так:
newtype Comp s a = Comp (ReaderT (Env s) (ST s) a)
runComp :: (forall s. Comp s a) -> a
runComp act = runST $ case act of
Comp c -> do
s <- newSTRef 0
runReaderT c (Env s)
Тогда вы можете написать
getSup :: Comp s (STRef s Int)
getSup = Comp (asks supply)