Làm thế nào để chơi với Control.Monad.Writer trong haskell?


97

Tôi mới học lập trình hàm và gần đây đang học tại Learn You a Haskell , nhưng khi tôi xem qua chương này , tôi đã gặp khó khăn với chương trình bên dưới:

import Control.Monad.Writer  

logNumber :: Int -> Writer [String] Int  
logNumber x = Writer (x, ["Got number: " ++ show x])  

multWithLog :: Writer [String] Int  
multWithLog = do  
    a <- logNumber 3  
    b <- logNumber 5  
    return (a*b)

Tôi đã lưu những dòng này trong tệp .hs nhưng không thể nhập nó vào ghci của tôi, điều này đã phàn nàn:

more1.hs:4:15:
    Not in scope: data constructor `Writer'
    Perhaps you meant `WriterT' (imported from Control.Monad.Writer)
Failed, modules loaded: none.

Tôi đã kiểm tra loại bằng lệnh ": info":

Prelude Control.Monad.Writer> :info Writer
type Writer w = WriterT w Data.Functor.Identity.Identity
               -- Defined in `Control.Monad.Trans.Writer.Lazy'

Theo quan điểm của tôi, điều này được cho là một cái gì đó giống như "newtype Writer wa ..." vì vậy tôi bối rối về cách cung cấp hàm tạo dữ liệu và nhận được một Writer.

Tôi đoán đó có thể là sự cố liên quan đến phiên bản và phiên bản ghci của tôi là 7.4.1


2
Đối với các bài tập, tôi khuyên bạn không nên nhập khai báo và tự ghi vào tệp.
sdcvvc

5
Tôi đã nói chuyện với tác giả một lúc trước, và anh ấy xác nhận rằng phiên bản trực tuyến của cuốn sách đã lỗi thời. Có một phiên bản cập nhật hơn trên PDF: [tại đây ]
Electric Coffee

@Electric, tôi cũng có câu hỏi tương tự, bạn có thể cho một liên kết được không? Liên kết trên của bạn bị hỏng.
Bulat M.

Câu trả lời:


127

Gói Control.Monad.Writerkhô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 writerhà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ờ logNumberlà 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 MonadWriterlớ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 MonadWriterlớ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 Writerlà 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 ReaderTbiế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" tellchứ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 wlà một monoid, và mlà a MonadWriter w, thì ReaderT r mcũng là a MonadWriter w. Điều này có nghĩa là chúng ta có thể sử dụng tellchứ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.


31
"Tôi đoán điều này đã khác khi LYAH được viết." Đúng. Điều đó đã thay đổi khi mtlchuyển từ phiên bản chính 1. * sang 2. *, ngay sau khi LYAH và RWH được viết. Thời gian cực kỳ đáng tiếc đã dẫn và dẫn đến nhiều nhầm lẫn cho những người mới bắt đầu.
Daniel Fischer,

2
Tôi đang sử dụng GHC phiên bản 7.8.3 ngay bây giờ và tôi phải nhập Control.Monad.Trans.Writer. Ngoài loại logNumberlogNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m adành cho tôi.
kmikael

@kmikael Có thể bạn chưa mtlcài đặt thư viện (có thể có nghĩa là bạn có cài đặt cơ bản của GHC, như minGHC, chứ không phải Nền tảng Haskell). Từ một chạy cửa sổ lệnh cabal updatecabal install mtlsau đó thử lại.
Chris Taylor

Chris, tôi đang sử dụng Nền tảng Haskell và mtl đã được cài đặt, tôi đã cài đặt lại nó và bây giờ nó hoạt động như trong câu trả lời của bạn. Tôi không biết điều gì đã xảy ra. Cảm ơn.
kmikael

Thay vào đó, bản in của cuốn sách là chính xác. Nó bao gồm một đoạn giải thích writerđược sử dụng thay cho Writerđoạn sau, giá trị ctor, không được xuất bởi mô-đun, trong khi đoạn trước là và nó có thể được sử dụng để tạo ra cùng một giá trị mà bạn sẽ tạo với ctor, nhưng không không cho phép khớp mẫu.
Enrico Maria De Angelis

8

Một hàm được gọi là "nhà văn" được tạo sẵn thay cho hàm tạo "Người viết". Thay đổi:

logNumber x = Writer (x, ["Got number: " ++ show x])

đến:

logNumber x = writer (x, ["Got number: " ++ show x])


6
Điều này bổ sung gì cho các câu trả lời hiện có?
dfeuer

1

Tôi nhận được một thông báo tương tự khi dùng thử LYAH "Để có thêm một vài môn học" bằng trình chỉnh sửa Haskell trực tuyến trong repl.it

Tôi đã thay đổi nhập từ:

import Control.Monad.Writer

đến:

import qualified Control.Monad.Trans.Writer.Lazy as W

Vì vậy, mã của tôi bây giờ hoạt động trông như thế này (với cảm hứng từ Blog Haskell của Kwang ):

import Data.Monoid
import qualified Control.Monad.Trans.Writer.Lazy as W


output :: String -> W.Writer [String] ()
output x = W.tell [x]


gcd' :: Int -> Int -> W.Writer [String] Int  
gcd' a b  
    | b == 0 = do  
        output ("Finished with " ++ show a)
        return a  
    | otherwise = do  
        output (show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b))
        gcd' b (a `mod` b)

main :: IO()
main = mapM_ putStrLn $ snd $ W.runWriter (gcd' 8 3) 

Mã hiện có thể chạy được ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.