it-roy-ru.com

Как работает монада ST?

Я понимаю, что монада ST - это что-то вроде младшего брата IO, который, в свою очередь, является монадой состояний с добавленной магией RealWorld. Я могу изобразить состояния и представить, что RealWorld каким-то образом помещен в IO, но каждый раз, когда я пишу сигнатуру типа ST, s монады ST смущает меня.

Взять, к примеру, ST s (STArray s a b). Как там работает s? Используется ли он просто для создания некоторой искусственной зависимости данных между вычислениями без возможности ссылаться на нее как на состояния в монаде состояний (из-за forall)?

Я просто выбрасываю идеи и буду очень признателен кому-то более знающему, чем я, чтобы объяснить это мне.

67
David

s удерживает объекты внутри монады ST от утечки наружу из монады ST.

-- This is an error... but let's pretend for a moment...
let a = runST $ newSTRef (15 :: Int)
    b = runST $ writeSTRef a 20
    c = runST $ readSTRef a
in b `seq` c

Хорошо, это ошибка типа (это хорошо! Мы не хотим, чтобы STRef просачивалась за пределы исходного вычисления!). Это ошибка типа из-за дополнительного s. Помните, что runST имеет подпись:

runST :: (forall s . ST s a) -> a

Это означает, что s в вычислении, которое вы выполняете, не должно иметь никаких ограничений. Поэтому, когда вы пытаетесь оценить a:

a = runST (newSTRef (15 :: Int) :: forall s. ST s (STRef s Int))

Результат будет иметь тип STRef s Int, что неверно, поскольку s "экранировано" за пределами forall в runST. Переменные типа всегда должны появляться внутри forall, и Haskell позволяет неявные квантификаторы forall везде. Там просто нет правила, которое позволяет вам осмысленно определить тип возвращаемого значения a.

Другой пример с forall: Чтобы ясно показать, почему нельзя позволить вещам избежать экранирования forall, вот более простой пример:

f :: (forall a. [a] -> b) -> Bool -> b
f g flag =
  if flag
  then g "abcd"
  else g [1,2]

> :t f length
f length :: Bool -> Int

> :t f id
-- error --

Конечно, f id является ошибкой, поскольку он будет возвращать либо список Char, либо список Int в зависимости от того, является ли логическое значение истинным или ложным. Это просто неправильно, как в примере с ST.

С другой стороны, если бы у вас не было параметра типа s, тогда все проверяло бы тип прекрасно, даже если код явно фальшивый.

Как на самом деле работает ST: С точки зрения реализации, монада ST фактически такая же, как монада IO, но с немного другим интерфейсом. Когда вы используете монаду ST, вы фактически получаете unsafePerformIO или его эквивалент за кулисами. Причина, по которой вы можете сделать это безопасно, заключается в том, что подпись типа всех функций, связанных с ST, особенно части с forall.

71
Dietrich Epp

s - это просто взлом, который заставляет систему типов останавливать ваши действия, которые были бы небезопасны. Он ничего не "делает" во время выполнения; это просто заставляет средство проверки типов отклонять программы, которые делают сомнительные вещи. (Это так называемый фантомный тип, вещь, которая существует только в голове средства проверки типов, и не влияет ни на что во время выполнения.)

26
MathematicalOrchid