Các s
giữ đố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 STRef
rò 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 runST
có chữ ký:
runST :: (forall s . ST s a) -> a
Điều này có nghĩa là s
tí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 forall
trong runST
. Các biến kiểu luôn phải xuất hiện bên trong a forall
và 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 id
là một lỗi, vì nó sẽ trả về một danh sách Char
hoặc một danh sách Int
tù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ó s
tham 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 unsafePerformIO
hoặ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ả ST
các hàm liên quan, đặc biệt là phần có forall
.