s
Зберігає об'єкти всередині ST
монади від витоку назовні з ST
монади.
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
. Змінні типу завжди повинні відображатися всередині a 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
Звичайно f id
, це помилка, оскільки вона повертає або список, Char
або список Int
залежно від того, логічне значення є істинним чи хибним. Це просто неправильно, як і приклад з ST
.
З іншого боку, якщо у вас не було s
параметра type, то все перевірятиме тип просто чудово, хоча код, очевидно, досить неправдивий.
Як насправді працює ST: з точки ST
зору реалізації, монада насправді така ж, як IO
монада, але з дещо іншим інтерфейсом. Коли ви використовуєте ST
монаду, яку фактично отримуєте, unsafePerformIO
або її еквівалент, за лаштунками. Причиною того, що ви можете зробити це безпечно, є ST
типова підпис усіх пов'язаних функцій, особливо частини з forall
.