จะเล่นกับ Control.Monad.Writer ใน Haskell ได้อย่างไร?


97

ฉันยังใหม่กับการเขียนโปรแกรมเชิงฟังก์ชันและเพิ่งเรียนรู้ที่Learn You a Haskellแต่เมื่อฉันอ่านบทนี้ฉันก็ติดอยู่กับโปรแกรมด้านล่าง:

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)

ฉันบันทึกบรรทัดเหล่านี้ในไฟล์. hs แต่ไม่สามารถนำเข้าสู่ ghci ของฉันซึ่งบ่น:

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

ฉันตรวจสอบประเภทโดยคำสั่ง ": info":

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

จากมุมมองของฉันนี่ควรจะเป็น "newtype Writer wa ... " ดังนั้นฉันจึงสับสนเกี่ยวกับวิธีป้อนตัวสร้างข้อมูลและรับ Writer

ฉันเดาว่าอาจเป็นปัญหาเกี่ยวกับเวอร์ชันและเวอร์ชัน ghci ของฉันคือ 7.4.1


2
สำหรับแบบฝึกหัดขอแนะนำว่าอย่านำเข้าใบประกาศและเขียนด้วยตัวเองในไฟล์
sdcvvc

5
ฉันได้พูดคุยกับผู้เขียนในขณะที่ย้อนกลับไปและเขายืนยันว่าหนังสือเวอร์ชันออนไลน์ล้าสมัย มีเวอร์ชั่นล่าสุดใน PDF: [ที่นี่ ]
Electric Coffee

@ ไฟฟ้าฉันมีคำถามเหมือนกันรบกวนขอลิงค์หน่อยได้ไหม ลิงก์ด้านบนของคุณเสีย
Bulat M.

คำตอบ:


127

แพคเกจไม่ได้ส่งออกคอนสตรัคข้อมูลControl.Monad.Writer Writerฉันเดาว่าสิ่งนี้แตกต่างกันเมื่อเขียน LYAH

ใช้เครื่องพิมพ์ดีด MonadWriter ใน ghci

คุณสร้างนักเขียนโดยใช้writerฟังก์ชันแทน ตัวอย่างเช่นในเซสชัน ghci ฉันสามารถทำได้

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

ตอนนี้logNumberเป็นฟังก์ชันที่สร้างนักเขียน ฉันสามารถขอประเภท:

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

ซึ่งบอกฉันว่าประเภทที่อนุมานไม่ใช่ฟังก์ชันที่ส่งกลับตัวเขียนเฉพาะแต่เป็นสิ่งที่ใช้MonadWriterคลาส type ตอนนี้ฉันสามารถใช้มันได้แล้ว:

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

(อินพุตจริงป้อนทั้งหมดในบรรทัดเดียว) นี่ฉันได้ระบุชนิดของการที่จะเป็นmultWithLog Writer [String] Intตอนนี้ฉันสามารถรันได้แล้ว:

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

และคุณจะเห็นว่าเราบันทึกการดำเนินการขั้นกลางทั้งหมด

ทำไมโค้ดถึงเขียนแบบนี้?

ทำไมต้องสร้างMonadWriterคลาสประเภทเลย? เหตุผลคือต้องทำกับหม้อแปลง monad ตามที่คุณทราบอย่างถูกต้องวิธีที่ง่ายที่สุดในการนำไปใช้Writerคือการใช้กระดาษห่อหุ้มแบบใหม่ที่ด้านบนของคู่:

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

คุณสามารถประกาศอินสแตนซ์ monad สำหรับสิ่งนี้แล้วเขียนฟังก์ชัน

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

ซึ่งเพียงแค่บันทึกข้อมูลเข้า ตอนนี้สมมติว่าคุณต้องการ monad ที่มีความสามารถในการบันทึก แต่ก็ทำอย่างอื่นได้เช่นกันบอกว่าสามารถอ่านจากสภาพแวดล้อมได้เช่นกัน คุณจะใช้สิ่งนี้เป็น

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

ตอนนี้เนื่องจากตัวเขียนอยู่ในReaderTหม้อแปลง monad หากคุณต้องการบันทึกเอาท์พุทคุณไม่สามารถใช้งานได้tell w(เพราะใช้งานได้เฉพาะกับนักเขียนที่ไม่ได้ห่อหุ้ม) แต่คุณต้องใช้lift $ tell wซึ่ง "ยก" tellฟังก์ชันผ่านReaderTเพื่อให้สามารถเข้าถึง นักเขียนภายใน monad หากคุณต้องการสองชั้นหม้อแปลง (ว่าคุณต้องการที่จะเพิ่มการจัดการข้อผิดพลาดเช่นกัน) lift $ lift $ tell wแล้วคุณจะต้องใช้ สิ่งนี้ทำให้เทอะทะได้อย่างรวดเร็ว

แต่โดยการกำหนดคลาสประเภทเราสามารถสร้าง monad transformer wrapper รอบ ๆ ตัวเขียนให้กลายเป็นตัวอย่างของตัวเขียน ตัวอย่างเช่น,

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

นั่นคือถ้าwเป็นหนังสือและmเป็นMonadWriter wแล้วนอกจากนี้ยังมีReaderT r m MonadWriter wซึ่งหมายความว่าเราสามารถใช้tellฟังก์ชั่นนี้ได้โดยตรงบนโมนาดที่เปลี่ยนรูปโดยไม่ต้องกังวลกับการยกมันผ่านหม้อแปลงโมนาด


31
"ฉันเดาว่าสิ่งนี้แตกต่างออกไปเมื่อเขียน LYAH" ขวา. ซึ่งเปลี่ยนmtlไปจากเวอร์ชันหลัก 1. * เป็น 2. * ไม่นานหลังจากที่เขียน LYAH และ RWH ช่วงเวลาที่โชคร้ายอย่างยิ่งทำให้เกิดความสับสนในหมู่ผู้เริ่มต้น
Daniel Fischer

2
ฉันใช้ GHC เวอร์ชัน 7.8.3 Control.Monad.Trans.Writerในขณะนี้และผมต้องนำเข้า นอกจากนี้ประเภทของการlogNumberเป็นlogNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m aสำหรับฉัน
kmikael

@kmikael คุณอาจไม่ได้mtlติดตั้งไลบรารี (ซึ่งอาจหมายความว่าคุณมีการติดตั้ง GHC พื้นฐานเช่น minGHC แทนที่จะเป็นแพลตฟอร์ม Haskell) จากคำสั่งที่เรียกใช้พรอมต์cabal updateและcabal install mtlแล้วลองอีกครั้ง
Chris Taylor

คริสฉันใช้แพลตฟอร์ม Haskell และติดตั้ง mtl แล้วฉันติดตั้งอีกครั้งและตอนนี้ดูเหมือนว่าจะใช้งานได้ในคำตอบของคุณ ฉันไม่รู้ว่าอะไรผิด ขอบคุณ.
kmikael

สำเนาหนังสือถูกต้องแทน ประกอบด้วยย่อหน้าที่อธิบายซึ่งwriterใช้แทนWriterค่า ctor หลังไม่ได้ถูกส่งออกโดยโมดูลในขณะที่อดีตคือและสามารถใช้เพื่อสร้างค่าเดียวกับที่คุณจะสร้างด้วย ctor แต่ทำ ไม่อนุญาตให้จับคู่รูปแบบ
Enlico

8

ฟังก์ชันที่เรียกว่า "writer" มีให้ใช้แทนตัวสร้าง "Writer" เปลี่ยนแปลง:

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

ถึง:

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


6
สิ่งนี้เพิ่มอะไรให้กับคำตอบที่มีอยู่
dfeuer

1

ฉันได้รับข้อความที่คล้ายกันจากการลองใช้ LYAH "For a few Monads More"โดยใช้ตัวแก้ไข Haskell ออนไลน์ในrepl.it

ฉันเปลี่ยนการนำเข้าจาก:

import Control.Monad.Writer

ถึง:

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

ตอนนี้รหัสของฉันใช้งานได้ในลักษณะนี้ (โดยได้รับแรงบันดาลใจจากบล็อก 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) 

ขณะนี้โค้ดสามารถทำงานได้ที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.