1. ホーム
  2. haskell

[解決済み] ハスケルでControl.Monad.Writerを遊ぶには?

2023-02-06 10:35:21

質問

私は関数型プログラミングの初心者で、最近、以下のサイトで学びました。 ハスケルを学ぶ で学習中ですが この章 を読み進めると、下のようなプログラムに引っかかってしまいました。

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 w a ..." のようなものであるはずでした。 ということで、データコンストラクタに与えてWriterを取得する方法について混乱しています。

バージョンの問題だと思うのですが、私のghciのバージョンは7.4.1です。

どのように解決するのですか?

パッケージ 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行にまとめて入力します)。ここでは multWithLogWriter [String] Int . これで実行できるようになりました。

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

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

なぜこのようなコードが書かれているのでしょうか?

なぜわざわざ MonadWriter 型クラスをわざわざ作るのでしょうか?その理由は、モナド変換に関係しています。あなたが正しく理解したように、最も簡単な実装方法は Writer を実装する最も簡単な方法は、ペアの上に新しい型のラッパーとして実装することです。

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 がモノイドであり mMonadWriter w であれば ReaderT r m はまた MonadWriter w . これはつまり tell 関数を、モナド変換器を通して明示的に持ち上げることに煩わされることなく、変換されたモナド上で直接使用することができることを意味します。