2012-07-06 28 views
5

Tôi đang cố gắng làm theo lời khuyên được đưa ra trong Combine state with IO actions để xây dựng một AppState cùng với một đơn nguyên IO. Những gì tôi nhận được là:Làm cách nào để thực thi một đơn vị StateT cùng với IO?

module Main where 

import Control.Monad.State 
import Control.Monad.Trans 

data ST = ST [Integer] deriving (Show) 
type AppState = StateT ST IO 

new = ST [] 

append :: Integer -> State ST() 
append v = state $ \(ST lst) -> ((), ST (lst ++ [v])) 

sumST :: State ST Integer 
sumST = state $ \(ST lst) -> (sum lst, ST lst) 

script = do 
    append 5 
    append 10 
    append 15 
    sumST 

myMain :: AppState() 
myMain = do 
    liftIO $ putStrLn "myMain start" 
    let (res, st) = runState script new 
    liftIO $ putStrLn $ show res 
    liftIO $ putStrLn "myMain stop" 

main = runStateT myMain (ST [15]) 

Có một số phần trong số này tôi không nhận được. Điều này làm tôi bực bội khi tôi có scriptmyMainmain. Nó cũng làm phiền tôi rằng tôi phải thực hiện runState trong vòng myMain và tôi phải cấp trạng thái ban đầu vào runStateT trong chức năng chính của mình. Tôi muốn có "kịch bản" của tôi, vì vậy để nói, trực tiếp trong hàm myMain vì toàn bộ điểm của myMain là để có thể chạy chắp thêm và tổng hợp trực tiếp trong myMain và ngay bên cạnh các thao tác in. Tôi nghĩ rằng tôi sẽ có thể làm được điều này, thay vì:

myMain :: AppState() 
myMain = do 
    liftIO $ putStrLn "myMain start" 
    append 5 
    append 10 
    append 15 
    r <- sumST 
    liftIO $ putStrLn $ show res 
    liftIO $ putStrLn "myMain stop" 

main = runState myMain 

Tôi đã nghĩ rằng điểm của máy biến áp đơn nguyên được vì vậy tôi có thể thực hiện các hoạt động đơn nguyên nhà nước của tôi trong một hàm (như trên) và nhấc hoạt động IO vào chức năng đó. Cách thích hợp để thiết lập tất cả những điều này để tôi có thể loại bỏ một trong các lớp của hướng dẫn là gì?


Ngoài giải pháp của Daniel (mà tôi đã gắn cờ giải pháp), tôi cũng tìm thấy một vài biến thể cũng có thể làm sáng tỏ tình hình. Đầu tiên, việc thực hiện cuối cùng của myMain và chính:

myMain :: AppState() 
myMain = do 
    liftIO $ putStrLn "myMain start" 
    append 5 
    append 10 
    append 15 
    res <- sumST 
    liftIO $ putStrLn $ show res 
    liftIO $ putStrLn "myMain stop" 

main = runStateT myMain new 

Bây giờ, triển khai khác nhau của append và sumST, ngoài Daniel:

append :: Integer -> AppState() 
append v = state $ \(ST lst) -> ((), ST (lst ++ [v])) 

sumST :: AppState Integer 
sumST = state $ \(ST lst) -> (sum lst, ST lst) 

và (lưu ý rằng chỉ có loại thay đổi tuyên bố, trong thực tế bạn có thể bỏ qua việc kê khai loại hoàn toàn!)

append :: MonadState ST m => Integer -> m() 
append v = state $ \(ST lst) -> ((), ST (lst ++ [v])) 

sumST :: MonadState ST m => m Integer 
sumST = state $ \(ST lst) -> (sum lst, ST lst) 

Nó xảy ra với tôi rằng các đơn nguyên AppState/StateT là không giống như cơ bản Nhà nước monad, và tôi đã được mã hóa cả hai sumST và phụ thêm cho nhà nước monad. Trong một nghĩa nào đó, họ cũng phải được nâng lên thành đơn vị StateT, mặc dù cách suy nghĩ đúng đắn là họ phải là chạy trong đơn nguyên (do đó, runState script new).

Tôi không chắc mình đã hoàn thành nó, nhưng tôi sẽ làm việc với nó một lúc, đọc mã MonadState và viết điều gì đó về điều này khi nó hoạt động trong đầu tôi.

+0

Ah, 'state' có nhiều hình dạng hơn tôi đã giả định, bởi vì tôi đã có nó trong đầu vì lý do nào đó hàm này là lời xin lỗi vì không xuất một hàm tạo' State' nữa. TIL! –

Trả lời

10

Vấn đề là bạn đã thực hiện các chức năng appendsumST quá đơn lẻ! Thay vì trực tiếp bằng cách sử dụng state chức năng, bạn nên sử dụng đa hình hơn getput chức năng, do đó bạn có thể cung cấp cho họ các loại thú vị hơn

append :: MonadState ST m => Integer -> m() 
append v = do 
    ST lst <- get 
    put (ST (lst ++ [v])) 

sumST :: MonadState ST m => m Integer 
sumST = do 
    ST lst <- get 
    return (sum lst) 

Sau đó, bạn có thể viết chính xác myMain bạn đề xuất (mặc dù bạn sẽ vẫn phải đưa ra một trạng thái ban đầu trong main). Là một điều phong cách, tôi sẽ đề xuất không xác định loại ST mới: có rất nhiều chức năng làm những việc tiện lợi với danh sách và khiến chúng không thể sử dụng bằng cách đặt một hàm tạo ST ở giữa bạn và danh sách làm phiền!Nếu bạn sử dụng [Integer] làm loại trạng thái của mình thay thế, bạn có thể tạo các định nghĩa như sau:

prepend :: MonadState [Integer] m => Integer -> m() 
prepend = modify . (:) 

sumST :: MonadState [Integer] m => m Integer 
sumST = gets sum 

Trông khá đẹp, phải không? =)

+0

Thực ra, mã hóa chắp thêm và sumST có ý nghĩa nếu bạn xem đây là một sự đơn giản hóa cho một kiểu dữ liệu phức tạp hơn. Giống như (trong ứng dụng thực sự của tôi) ST {datastore :: MyData, event_stream :: [Events]} –

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