HaskellでControl.Monad.Writerを使用する方法


96

私は関数型プログラミングが初めてで、最近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 2012

5
私はしばらく前に著者に話しました、そして彼は本のオンライン版が古くなっていることを確認しました。PDFには、より最新のバージョンがあります。[ここ ]
Electric Coffee

@電気、私も同じ質問があります。リンクをください。上記のリンクは壊れています。
Bulat M.17年

回答:


126

パッケージControl.Monad.WriterはデータコンストラクターをエクスポートしませんWriter。LYAHが書かれたとき、これは異なっていたと思います。

ghciでのMonadWriterタイプクラスの使用

代わりに、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型クラスを実装するすべてのものであることを示しています。これで使用できます:

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

(入力は実際にはすべて1行で入力されています)。ここでは、のタイプをmultWithLogに指定しましたWriter [String] Int。これで実行できます:

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

そして、すべての中間操作をログに記録していることがわかります。

なぜコードはこのように書かれているのですか?

なぜMonadWriter型クラスを作成するのが面倒なのですか?その理由は、モナド変換子を処理するためです。正しく理解したように、実装する最も簡単な方法Writerは、ペアの上にnewtypeラッパーを置くことです。

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

これのモナドインスタンスを宣言し、関数を書くことができます

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

入力をログに記録するだけです。ここで、ロギング機能を備えたモナドが必要だとしますが、他のことも行います-環境からも読み取ることができるとしましょう。これを次のように実装します

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

今、作家は内部にあるので、ReaderTあなたが使用することはできません出力をログに記録したい場合は、モナド変圧器tell w(つまりのみ開封された作家で動作するので)しかし、あなたは使用する必要がありlift $ tell w、これは「リフト」tellを通じて機能をReaderTので、それがアクセスできることインナーライターモナド。2層のトランスフォーマが必要な場合(たとえば、エラー処理も追加したい場合)、を使用する必要がありますlift $ lift $ tell w。これはすぐに扱いにくくなります。

代わりに、型クラスを定義することで、ライターの周りのモナド変換ラッパーをライター自体のインスタンスにできます。例えば、

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

つまりw、がモノイドであり、mであるMonadWriter w場合、ReaderT r mMonadWriter wです。これはtell、モナド変換器を介して明示的に持ち上げる必要なしに、変換されたモナドで関数を直接使用できることを意味します。


31
「LYAHが書かれたとき、これは異なっていたと思います。」正しい。これで変更mtlLYAHとRWHが書き込まれた直後に、メジャーバージョンから1 * 2 *に行きます。初心者の間で多くの混乱を導き、そして導きました、非常に不幸なタイミング。
ダニエルフィッシャー

2
現在GHCバージョン7.8.3を使用しており、インポートする必要がありましたControl.Monad.Trans.Writer。またのタイプlogNumberlogNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m a私用です。
kmikael

@kmikaelおそらくmtlライブラリがインストールされていません(つまり、HaskellプラットフォームではなくminGHCなどのGHCの基本インストールがあることを意味します)。コマンドプロンプトから実行cabal updatecabal install mtl、もう一度やり直してください。
Chris Taylor

クリス、私はHaskellプラットフォームを使用していて、mtlがインストールされていましたが、もう一度インストールしましたが、今はそれがあなたの答えのように機能しているようです。何が悪かったのかわかりません。ありがとう。
kmikael

代わりに、印刷された本のコピーが正しいです。後者のwriter代わりに使用されることを説明する段落が含まれています。Writer後者は値のctorであり、モジュールによってエクスポートされません。一方、前者はモジュールでエクスポートされ、ctorで作成するのと同じ値を作成するために使用できますが、パターンマッチングを許可しません。
エンリコマリアデアンジェリス

8

「ライター」と呼ばれる関数は、「ライター」コンストラクターの代わりに使用可能になります。変化する:

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

に:

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


6
これは既存の回答に何を追加しますか?
dfeuer 2016年

1

repl.it のオンラインHaskellエディタを使用してLYAH "For a some Monads More"を試したところ、同様のメッセージが表示されました

インポートを次のように変更しました:

import Control.Monad.Writer

に:

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

したがって、私のコードは次のように動作します(KwangのHaskellブログからのインスピレーションを使用)。

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.