Неявное выполнение STRef в среде во время вычислений - PullRequest
0 голосов
/ 23 января 2019

Я работаю над некоторыми большими вычислениями, которые требуют использования изменяемых данных в некоторые критические моменты.Я хочу как можно больше избегать 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 s).

Функция, которую я пытаюсь реализовать, заключается в том, чтобы сделать supply доступным в любое время, но не переданным явно (это не заслуживает этого).Самое удобное место для его хранения - это окружение, но я не вижу способа его инициализации.

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

Как правильно спроектировать эту модель?Под «правильно» я подразумеваю не только «проверку типов», но и «быть вежливым с остальным кодом» и «максимально гибко».

1 Ответ

0 голосов
/ 23 января 2019

runST должен быть задан полиморфный аргумент, и вы хотите, чтобы ваш аргумент исходил из Comp. Ergo 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)
...