101

私は関数型プログラミングの初心者で、最近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 は ...」のようなものになるはずだったので、データ コンストラクターをフィードして Writer を取得する方法について混乱しています。

バージョン関連の問題である可能性があり、私のghciバージョンは7.4.1です

4

3 に答える 3

131

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

ghci で MonadWriter 型クラスを使用する

代わりに、writer関数を使用してライターを作成します。たとえば、ghci セッションでできること

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

NowlogNumberはライターを作成する関数です。そのタイプを尋ねることができます:

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が aのMonadWriter w場合、ReaderT r mMonadWriter wです。これはtell、変換されたモナドで関数を直接使用できることを意味します。モナド変換子を介して明示的に持ち上げる必要はありません。

于 2012-07-27T08:56:07.363 に答える
9

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

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

に:

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

于 2016-05-18T01:27:22.743 に答える