Gói Control.Monad.Writer
không xuất hàm tạo dữ liệu Writer
. Tôi đoán điều này đã khác khi LYAH được viết.
Sử dụng kiểu chữ MonadWriter trong ghci
Thay vào đó, bạn tạo người viết bằng cách sử dụng writer
hàm. Ví dụ: trong một phiên ghci, tôi có thể làm
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
Bây giờ logNumber
là một chức năng tạo ra các nhà văn. Tôi có thể yêu cầu loại của nó:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
Điều này cho tôi biết rằng kiểu được suy ra không phải là một hàm trả về một trình viết cụ thể , mà là bất cứ thứ gì triển khai MonadWriter
lớp kiểu. Bây giờ tôi có thể sử dụng nó:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Đầu vào thực sự nhập tất cả trên một dòng). Ở đây tôi đã chỉ định loại multWithLog
được Writer [String] Int
. Bây giờ tôi có thể chạy nó:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
Và bạn thấy rằng chúng tôi ghi lại tất cả các hoạt động trung gian.
Tại sao mã được viết như thế này?
Tại sao phải tạo ra các MonadWriter
lớp kiểu? Lý do là làm với máy biến áp đơn nguyên. Như bạn đã nhận ra một cách chính xác, cách đơn giản nhất để triển khai Writer
là như một trình bao bọc kiểu mới trên đầu một cặp:
newtype Writer w a = Writer { runWriter :: (a,w) }
Bạn có thể khai báo một cá thể đơn nguyên cho việc này, rồi viết hàm
tell :: Monoid w => w -> Writer w ()
mà chỉ cần ghi lại đầu vào của nó. Bây giờ, giả sử bạn muốn một đơn nguyên có khả năng ghi nhật ký, nhưng cũng có thể thực hiện một việc khác - giả sử nó cũng có thể đọc từ một môi trường. Bạn sẽ triển khai điều này với tư cách là
type RW r w a = ReaderT r (Writer w a)
Bây giờ vì trình ghi nằm bên trong ReaderT
biến áp đơn nguyên, nếu bạn muốn ghi đầu ra, bạn không thể sử dụng tell w
(vì điều đó chỉ hoạt động với các trình ghi chưa được bọc) nhưng bạn phải sử dụng lift $ tell w
, điều này "nâng" tell
chức năng thông qua ReaderT
để nó có thể truy cập đơn nguyên nhà văn bên trong. Nếu bạn muốn máy biến áp hai lớp (giả sử bạn cũng muốn thêm xử lý lỗi) thì bạn cần sử dụng lift $ lift $ tell w
. Điều này nhanh chóng trở nên khó sử dụng.
Thay vào đó, bằng cách xác định một lớp kiểu, chúng ta có thể biến bất kỳ trình bao bọc biến áp đơn nguyên nào xung quanh một trình viết thành một thể hiện của chính trình viết. Ví dụ,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
nghĩa là, nếu w
là một monoid, và m
là a MonadWriter w
, thì ReaderT r m
cũng là a MonadWriter w
. Điều này có nghĩa là chúng ta có thể sử dụng tell
chức năng trực tiếp trên đơn nguyên đã biến đổi, mà không cần phải bận tâm đến việc nâng nó một cách rõ ràng qua biến áp đơn nguyên.