Bagaimana cara bermain dengan Control.Monad.Writer di haskell?

97

Saya baru mengenal pemrograman fungsional dan baru-baru ini belajar di Learn You a Haskell , tetapi ketika saya melewati bab ini , saya terjebak dengan program di bawah ini:

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)

Saya menyimpan baris-baris ini dalam file .hs dan tetapi gagal mengimpornya ke ghci saya yang mengeluh:

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

Saya memeriksa jenis dengan perintah ": info":

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

Dari sudut pandang saya, ini seharusnya menjadi sesuatu seperti "Newtype Writer wa ..." jadi saya bingung tentang cara memberi makan konstruktor data dan mendapatkan Writer.

Saya kira ini mungkin masalah terkait versi dan versi ghci saya adalah 7.4.1

Javran
sumber
2
Untuk latihan, saya sarankan untuk tidak mengimpor deklarasi dan menuliskannya sendiri di file.
sdcvvc
5
Saya berbicara dengan penulis beberapa waktu yang lalu, dan dia memastikan bahwa versi online dari buku tersebut sudah ketinggalan zaman. Ada versi yang lebih baru di PDF: [di sini ]
Electric Coffee
@Listrik, saya memiliki pertanyaan yang sama, bisakah Anda memberikan tautannya? Tautan Anda di atas rusak.
Bulat M.
2
@Bulat. Ini dia
Electric Coffee

Jawaban:

127

Paket Control.Monad.Writertidak mengekspor konstruktor data Writer. Saya rasa ini berbeda ketika LYAH ditulis.

Menggunakan kelas tipe MonadWriter di ghci

Sebaliknya, Anda membuat penulis menggunakan writerfungsi tersebut. Misalnya, dalam sesi ghci yang bisa saya lakukan

ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])

Sekarang logNumberadalah fungsi yang menciptakan penulis. Saya bisa menanyakan tipenya:

ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a

Yang memberi tahu saya bahwa tipe yang disimpulkan bukanlah fungsi yang mengembalikan penulis tertentu , melainkan apa pun yang mengimplementasikan MonadWriterkelas tipe. Saya sekarang dapat menggunakannya:

ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
    :: Writer [String] Int

(Input sebenarnya memasukkan semua dalam satu baris). Di sini saya telah menentukan tipe multWithLogmenjadi Writer [String] Int. Sekarang saya bisa menjalankannya:

ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])

Dan Anda melihat bahwa kami mencatat semua operasi perantara.

Mengapa kode ditulis seperti ini?

Mengapa repot-repot membuat MonadWriterkelas tipe? Alasannya ada hubungannya dengan trafo monad. Seperti yang Anda sadari dengan benar, cara termudah untuk mengimplementasikan Writeradalah sebagai pembungkus tipe baru di atas pasangan:

newtype Writer w a = Writer { runWriter :: (a,w) }

Anda dapat mendeklarasikan instance monad untuk ini, lalu menulis fungsinya

tell :: Monoid w => w -> Writer w ()

yang hanya mencatat masukannya. Sekarang misalkan Anda menginginkan monad yang memiliki kemampuan logging, tetapi juga melakukan sesuatu yang lain - katakanlah ia juga dapat membaca dari lingkungan. Anda akan menerapkan ini sebagai

type RW r w a = ReaderT r (Writer w a)

Sekarang karena penulis berada di dalam ReaderTtrafo monad, jika Anda ingin mencatat keluaran Anda tidak dapat menggunakan tell w(karena itu hanya beroperasi dengan penulis yang tidak terbungkus) tetapi Anda harus menggunakan lift $ tell w, yang "mengangkat" tellfungsi melalui ReaderTsehingga dapat mengakses penulis batin monad. Jika Anda menginginkan transformator dua lapisan (katakanlah Anda ingin menambahkan penanganan kesalahan juga) maka Anda perlu menggunakan lift $ lift $ tell w. Ini dengan cepat menjadi berat.

Sebaliknya, dengan mendefinisikan kelas tipe kita dapat membuat pembungkus trafo monad di sekitar penulis menjadi instance penulis itu sendiri. Sebagai contoh,

instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)

yaitu, jika wmonoid, dan mmerupakan a MonadWriter w, maka ReaderT r mjuga a MonadWriter w. Artinya kita dapat menggunakan tellfungsi tersebut secara langsung pada monad yang ditransformasikan, tanpa harus repot-repot mengangkatnya secara eksplisit melalui trafo monad.

Chris Taylor
sumber
31
"Saya kira ini berbeda ketika LYAH ditulis." Baik. Itu berubah dengan beralih mtldari versi mayor 1. * ke 2. *, tak lama setelah LYAH dan RWH ditulis. Waktu yang sangat disayangkan yang menyebabkan dan menyebabkan banyak kebingungan di kalangan pemula.
Daniel Fischer
2
Saya menggunakan GHC versi 7.8.3 sekarang dan saya harus mengimpor Control.Monad.Trans.Writer. Selain itu, tipe logNumberini logNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m auntuk saya.
kmikael
@kmikael Anda mungkin belum mtlmenginstal library (yang mungkin berarti Anda memiliki instalasi dasar GHC, seperti minGHC, bukan Platform Haskell). Dari perintah run yang cepat cabal updatedan cabal install mtlkemudian coba lagi.
Chris Taylor
Chris, saya menggunakan Platform Haskell dan mtl telah diinstal, saya menginstalnya lagi dan sekarang sepertinya berfungsi seperti di jawaban Anda. Saya tidak tahu apa yang salah. Terima kasih.
kmikael
Alih-alih, salinan cetak buku tersebut benar. Ini termasuk paragraf yang menjelaskan yang writerdigunakan sebagai pengganti Writersebagai yang terakhir, nilai ctor, tidak diekspor oleh modul, sedangkan yang pertama adalah, dan dapat digunakan untuk membuat nilai yang sama yang akan Anda buat dengan ctor, tetapi tidak tidak mengizinkan pencocokan pola.
Enrico Maria De Angelis
8

Fungsi yang disebut "penulis" tersedia sebagai pengganti konstruktor "Penulis". Perubahan:

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

untuk:

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

Marcus
sumber
6
Apa yang ditambahkan ini pada jawaban yang sudah ada?
dfeuer
1

Saya mendapat pesan serupa dari mencoba LYAH "For a few Monads More" menggunakan editor Haskell online di repl.it

Saya mengubah impor dari:

import Control.Monad.Writer

untuk:

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

Jadi kode saya sekarang berfungsi seperti ini (dengan inspirasi dari Blog Haskell 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) 

Kode saat ini dapat dijalankan di sini

Simon Dowdeswell
sumber