Неявное выполнение 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)
Другие вопросы по тегам