TL; DR. Tất cả bốn trong số các chức năng này chỉ là các kiểu báo. Tất cả chúng đều không có sẵn trong thời gian chạy. Chỉ có chỉ có khác biệt giữa chúng là chữ ký loại — nhưng đó là chữ ký kiểu thực thi tất cả các đảm bảo an toàn ngay từ đầu!
Các ST
đơn nguyên và IO
đơn nguyên cả cung cấp cho bạn trạng thái có thể thay đổi.
Điều nổi tiếng là không thể thoát khỏi đơn vị IO
. [Vâng, không, bạn có thể nếu bạn sử dụng unsafePerformIO
. Đừng làm điều đó!] Bởi vì điều này, tất cả các I/O mà chương trình của bạn sẽ bao giờ thực hiện được đóng gói thành một khối khổng lồ IO
khổng lồ, do đó thực thi một trật tự toàn cầu về các hoạt động. [Ít nhất, cho đến khi bạn gọi forkIO
, nhưng dù sao ...]
Lý do unsafePerformIO
như vậy nguyền rủa không an toàn là có không có cách nào để tìm ra chính xác khi nào, nếu, hoặc có bao nhiêu lần tôi kèm theo/Các hoạt động O sẽ xảy ra — thường là điều rất xấu.
Các ST
đơn nguyên cũng cung cấp nhà nước có thể thay đổi, nhưng nó không có một cơ chế thoát — các runST
chức năng. Điều này cho phép bạn biến một giá trị không tinh khiết thành một giá trị thuần túy. Nhưng bây giờ có không có cách nào để đảm bảo thứ tự riêng biệt ST
khối sẽ chạy. Để ngăn chặn sự tàn phá hoàn toàn, chúng tôi cần đảm bảo rằng các khối ST
riêng biệt không thể "can thiệp" với nhau.
Vì lý do đó, bạn không thể thực hiện bất kỳ hoạt động I/O nào trong đơn lẻ ST
. Bạn có thể truy cập trạng thái có thể thay đổi, nhưng trạng thái đó không được phép thoát khỏi khối ST
.
Các IO
đơn nguyên và ST
đơn nguyên đang thực sự cùng đơn nguyên. Và một IORef
thực sự là một STRef
, v.v. Vì vậy, nó sẽ thực sự bởi jolly hữu ích để có thể viết mã và sử dụng nó trong cả hai monads. Và tất cả bốn chức năng mà bạn đề cập là loại phôi cho phép bạn thực hiện chính xác điều đó.
Để hiểu sự nguy hiểm, chúng ta cần hiểu cách ST
đạt được mẹo nhỏ của nó. Đó là tất cả trong loại phantom s
trong các chữ ký loại. Để chạy một khối ST
, nó cần phải làm việc cho tất cả các thể s
:
runST :: (forall s. ST s x) -> x
Tất cả những thứ có thể thay đổi có s
trong các loại là tốt, và bởi một tai nạn hạnh phúc, đó có nghĩa là bất kỳ nỗ lực để trở thứ có thể thay đổi ra của ST
đơn nguyên sẽ bị đánh máy. (Đây thực sự là một chút hack, nhưng nó hoạt động rất hoàn hảo ...)
Ít nhất, nó sẽ bị đánh máy nếu bạn sử dụng runST
. Lưu ý rằng ioToST
cung cấp cho bạn ST RealWorld x
. Nói chung, IO x
& xấp xỉ; ST RealWorld x
. Nhưng runST
sẽ không chấp nhận điều đó làm đầu vào. Vì vậy, bạn không thể sử dụng runST
để chạy I/O.
ioToST
cung cấp cho bạn loại mà bạn không thể sử dụng với runST
. Nhưng unsafeIOToST
cung cấp cho bạn loại hoạt động tốt với runST
. Vào thời điểm đó, bạn đã cơ bản thực hiện unsafePerformIO
:
unsafePerformIO = runST . ioToST
Các unsafeSTToIO
cho phép bạn để có được những thứ có thể thay đổi ra khỏi một ST
khối, và có khả năng thành khác:
foobar = do
v <- unsafeSTToIO (newSTRef 42)
let w = runST (readSTRef v)
let x = runST (writeSTRef v 99)
print w
Bạn muốn khám phá một đoán gì đang xảy ra để được in? Bởi vì vấn đề là, chúng tôi có ba hành động ST
ở đây, điều này có thể xảy ra theo bất kỳ thứ tự nào. readSTRef
có xảy ra trước hoặc sau writeSTRef
không?
[Thực ra, trong ví dụ này, viết không bao giờ xảy ra, bởi vì chúng tôi không "làm" bất cứ điều gì với x
. Nhưng nếu tôi vượt qua x
đến một số phần không liên quan, không liên quan của mã và mã đó xảy ra để kiểm tra nó, đột nhiên hoạt động I/O của chúng tôi thực hiện điều gì đó khác. đang tinh khiết không sẽ có thể ảnh hưởng đến những thứ có thể thay đổi như thế]
Chỉnh sửa: Có vẻ tôi đã hơi sớm. Chức năng unsafeSTToIO
cho phép bạn lấy một giá trị có thể thay đổi ra khỏi ST
đơn lẻ, nhưng có vẻ như nó yêu cầu một cuộc gọi thứ hai tới unsafeSTToIO
để đặt điều có thể thay đổi trở lại thành lại ST
đơn lẻ. (Tại thời điểm đó, cả hai hành động này IO
hành động, vì vậy đặt hàng của họ được đảm bảo.)
Bạn có thể dĩ nhiên kết hợp trong một số unsafeIOToST
là tốt, nhưng điều đó không thực sự chứng minh rằng unsafeSTToIO
bởi chính nó là không an toàn:
foobar = do
v <- unsafeSTToIO (newSTRef 42)
let w = runST (unsafeIOToST $ unsafeSTToIO $ readSTRef v)
let x = runST (unsafeIOToST $ unsafeSTToIO $ writeSTRef v 99)
print w
tôi đã chơi xung quanh với điều này, và tôi vẫn chưa được quản lý để thuyết phục các kiểm tra loại để cho tôi làm điều gì đó có thể chứng minh không an toàn sử dụng chỉunsafeSTToIO
. Tôi vẫn tin rằng nó có thể được thực hiện, và các ý kiến khác nhau về câu hỏi này dường như đồng ý, nhưng tôi không thể thực sự xây dựng một ví dụ. Bạn có được ý tưởng mặc dù; thay đổi các loại và sự an toàn của bạn bị hỏng.
Sự khác biệt là trong các loại.ioToST chỉ có thể tạo ra 'ST RealWorld a' mà bạn không thể chuyển tới' runST :: (forall s. ST s a) -> a'. – user2407038