Các sgiữ đối tượng bên trong STđơn nguyên từ rò rỉ ra bên ngoài của các STđơn nguyên.
let a = runST $ newSTRef (15 :: Int)
b = runST $ writeSTRef a 20
c = runST $ readSTRef a
in b `seq` c
Được rồi, đây là lỗi kiểu (đó là một điều tốt! Chúng tôi không muốn STRefrò rỉ ra bên ngoài tính toán ban đầu!). Đó là một lỗi loại vì phần phụ s. Hãy nhớ rằng runSTcó chữ ký:
runST :: (forall s . ST s a) -> a
Điều này có nghĩa là stính toán mà bạn đang chạy không có ràng buộc nào đối với nó. Vì vậy, khi bạn cố gắng đánh giá a:
a = runST (newSTRef (15 :: Int) :: forall s. ST s (STRef s Int))
Kết quả sẽ có loại STRef s Int, đó là sai vì sđã "thoát" bên ngoài foralltrong runST. Các biến kiểu luôn phải xuất hiện bên trong a forallvà Haskell cho phép các bộ forallđịnh lượng ngầm ở mọi nơi. Đơn giản là không có quy tắc nào cho phép bạn tìm ra kiểu trả về một cách có ý nghĩa a.
Một ví dụ khác với forall: Để hiển thị rõ ràng lý do tại sao bạn không thể cho phép mọi thứ thoát khỏi a forall, đây là một ví dụ đơn giản hơn:
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
Tất nhiên f idlà một lỗi, vì nó sẽ trả về một danh sách Charhoặc một danh sách Inttùy thuộc vào việc boolean là true hay false. Nó chỉ đơn giản là sai, giống như ví dụ với ST.
Mặt khác, nếu bạn không có stham số kiểu thì mọi thứ sẽ kiểm tra kiểu tốt, mặc dù mã rõ ràng là không có thật.
Cách ST thực sự hoạt động: Về mặt triển khai, STđơn nguyên thực sự giống với IOđơn nguyên nhưng có giao diện hơi khác. Khi bạn sử dụng STđơn nguyên bạn thực sự nhận được unsafePerformIOhoặc tương đương, ở hậu trường. Lý do bạn có thể thực hiện việc này một cách an toàn là vì chữ ký kiểu của tất cả STcác hàm liên quan, đặc biệt là phần có forall.