2015-02-27 11 views
11

Sự khác biệt và mục đích sử dụng có thể là gì đối với ioToSTunsafeSTToIO được xác định trong GHC.IO?Sự khác nhau giữa `ioToST` và 'unsafeIOToST` từ GHC.IO

-- --------------------------------------------------------------------------- 

-- Coercions between IO and ST 

-- | A monad transformer embedding strict state transformers in the 'IO' 
-- monad. The 'RealWorld' parameter indicates that the internal state 
-- used by the 'ST' computation is a special one supplied by the 'IO' 
-- monad, and thus distinct from those used by invocations of 'runST'. 
stToIO  :: ST RealWorld a -> IO a 
stToIO (ST m) = IO m 

ioToST  :: IO a -> ST RealWorld a 
ioToST (IO m) = (ST m) 

-- This relies on IO and ST having the same representation modulo the 
-- constraint on the type of the state 
-- 
unsafeIOToST  :: IO a -> ST s a 
unsafeIOToST (IO io) = ST $ \ s -> (unsafeCoerce# io) s 

unsafeSTToIO :: ST s a -> IO a 
unsafeSTToIO (ST m) = IO (unsafeCoerce# m) 
+5

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

Trả lời

12

Các phiên bản an toàn phải bắt đầu trong đơn nguyên IO (vì bạn không thể có được một ST RealWorld từ runST) và cho phép bạn chuyển đổi giữa bối cảnh IO và một bối cảnh ST RealWorld. Chúng an toàn vì ST RealWorld về cơ bản giống với IO.

Phiên bản không an toàn có thể bắt đầu bất cứ nơi nào (vì runST có thể được gọi ở bất kỳ đâu) và cho phép bạn chuyển đổi giữa đơn nguyên ST tùy ý và đơn IO. Sử dụng runST từ ngữ cảnh thuần túy và sau đó thực hiện unsafeIOToST trong đơn vị tiểu bang về cơ bản tương đương với việc sử dụng unsafePerformIO.

+4

Tôi biết đó không phải là câu hỏi của OP, nhưng trong đầu tôi, câu hỏi lớn hơn là, nếu có, làm cho 'unsafeSTToIO' không an toàn. – dfeuer

+1

@dfeuer Nó có thể được gọi là không an toàn để đối xứng với 'unsafeIOToST', hoặc có thể bạn sẽ tìm cách sử dụng nó để truy cập' STVar' bên ngoài trình bao bọc 'runST', trong trường hợp đó sẽ thực sự không an toàn chinh no. –

+2

Tôi tìm thấy một chủ đề trên haskell-cafe về nó. Có vẻ như nó thực sự không an toàn, nhưng tôi không hiểu bất kỳ ví dụ nào. – dfeuer

9

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.

+3

"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." Có một cách rất sạch sẽ để hoàn thành mục đích này mà không có bất cứ điều gì không an toàn: ['primitive'] (https://hackage.haskell.org/package/primitive) cung cấp một lớp' PrimMonad' với các thể hiện 'ST s' và' IO' và chỉ là về mọi thứ bạn có thể muốn viết các hàm có thể xử lý với một trong hai ví dụ. Những chuyển đổi này chỉ cần thiết để đối phó với thực tế là nhiều chức năng thư viện là (không cần thiết) cụ thể. – dfeuer

+1

Er ... Tôi vừa thử, và chương trình của bạn không gõ. Các ví dụ thực tế dường như khá tinh tế hơn. – dfeuer

+2

Tôi tin rằng ví dụ 'foobar' không thể hoạt động. Lý do là 'runST' cần một giá trị polytyped, trong khi' v' là kiểu đơn. Thật vậy, 'unsafeSTToIO (newSTRef 42)' có kiểu 'forall s. IO (STRef s Int) 'và không phải' IO (forall s. STRef s Int) '. – chi

Các vấn đề liên quan