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