11

Haskell 学習演習としてスパイダー ソリティア プレーヤーを作成しようとしています。

私の関数は、ゲームごとに 1 回main関数を呼び出し( を使用)、ゲーム番号と乱数発生器 ( ) を渡します。この関数はモナドと IO モナドを返す必要があります。IO モナドには、ゲーム テーブルを表示し、ゲームが勝ったか負けたかを示します。playGamemapMStdGenplayGameControl.Monad.StateStringBool

戻り値Stateのモナドとモナドを組み合わせるにはどうすればよいですか? IO`playGame の型宣言はどうあるべきか?

playGame :: Int -> StdGen a -> State IO (String, Bool)

State IO (String, Bool)正しいですか?そうでない場合、それは何ですか?

main、使用する予定です

do
  -- get the number of games from the command line (already written)
  results <- mapM (\game -> playGame game getStdGen) [1..numberOfGames]

これは正しい呼び出し方playGameですか?

4

3 に答える 3

17

あなたが望むのは、(パッケージから)と(パッケージから)の両方によって提供されるStateT s IO (String, Bool)場所です。StateTControl.Monad.StatemtlControl.Monad.Trans.Statetransformers

この一般的な現象はモナド変換子と呼ばれ、 Monad Transformers, Step by Stepで素晴らしい紹介を読むことができます。

それらを定義するには 2 つのアプローチがあります。それらの1つは、クラスをtransformers使用してそれらを実装するパッケージにあります。MonadTrans2 番目のアプローチはクラス内にmtlあり、各モナドに個別の型クラスを使用します。

このアプローチの利点はtransformers、単一の型クラスを使用してすべてを実装することです (ここにあります)。

class MonadTrans t where
    lift :: Monad m => m a -> t m a

liftMonadTransのすべてのインスタンスが満たさなければならない2 つの優れたプロパティがあります。

(lift .) return = return
(lift .) f >=> (lift .) g = (lift .) (f >=> g)

これらは変装した関手法則であり、(lift .) = fmapwherereturn = id(>=>) = (.).

型クラスのmtlアプローチにも利点があり、型クラスを使用してのみ明確に解決できるものもありますがmtl、欠点は、各mtl型クラスには、インスタンスを実装するときに覚えておく必要がある独自の規則があることです。 . たとえば、MonadError型クラス (ここにあります) は次のように定義されます。

class Monad m => MonadError e m | m -> e where
    throwError :: e -> m a
    catchError :: m a -> (e -> m a) -> m a

このクラスにも法則が付属しています。

m `catchError` throwError = m
(throwError e) `catchError` f = f e
(m `catchError` f) `catchError` g = m `catchError` (\e -> f e `catchError` g)

これらは単なる変装したモナド則 wherethrowError = returncatchError = (>>=)(そしてモナド則は変装した圏則 wherereturn = id(>=>) = (.)) です。

特定の問題について、プログラムを作成する方法は同じです。

do
  -- get the number of games from the command line (already written)
  results <- mapM (\game -> playGame game getStdGen) [1..numberOfGames]

...しかし、playGame関数を記述すると、次のようになります。

-- transformers approach :: (Num s) => StateT s IO ()
do x <- get
   y <- lift $ someIOAction
   put $ x + y

-- mtl approach :: (Num s, MonadState s m, MonadIO m) => m ()
do x <- get
   y <- liftIO $ someIOAction
   put $ x + y

複数のモナド変換子を積み上げ始めると、より明白になるアプローチ間の違いがもっとありますが、今のところは良いスタートだと思います。

于 2012-06-06T16:56:16.727 に答える
8

StateはモナドでありIO、モナドです。あなたがゼロから書こうとしているものは「モナド変換子」と呼ばれ、Haskell 標準ライブラリはすでに必要なものを定義しています。

ステート モナド トランスフォーマーを見てStateTくださいState

各モナドトランスフォーマーは、インスタンスごとに、トランスフォーマーができるたびにそれを処理するように、一連の型クラスを実装します (たとえば、状態トランスフォーマーは状態関連の関数のみを直接処理できます)。または、内部モナドへの呼び出しを伝播します。必要なすべてのトランスフォーマーを積み重ねることができ、それらすべての機能にアクセスするための統一されたインターフェイスを使用できるようにします。このように見たいのであれば、それは一種の責任の連鎖です。

hackageを調べたり、スタック オーバーフローや Google で簡単に検索したりすると、 の使用例がたくさん見つかりますStateT

編集: 別の興味深い読み物はMonad Transformers Explainedです。

于 2012-06-06T12:13:02.403 に答える
2

さて、ここで明確にすることがいくつかあります。

  • 「モナドを返す」ことはできません。モナドはの一種であり、値の一種ではありません (正確には、モナドはクラスのインスタンスを持つ型構築子です)。Monadこれは衒学的に聞こえるかもしれませんが、頭の中で物事と物事の種類の区別を整理するのに役立つかもしれません。これは重要です.
  • それなしでは何もできないことに注意してくださいState. 多くの場合、必要な通常の関数型を書くだけで、Thing -> (Thing, a)「あはは、これは少し似ているようStateに見えます。おそらくこれはState Thing a. State単純な関数を理解して操作することは、またはその友達を使用するための重要な第一歩です。
  • IO、一方、その仕事をすることができる唯一のものです。しかし、playGameI/O を実行する必要があるものの名​​前として、その名前がす​​ぐに思い浮かぶわけではありません。特に、(疑似) 乱数のみIOが必要な場合は、 . コメンターが指摘したように、MonadRandomStdGenはこれを単純にするのに最適ですが、 fromを受け取って返す純粋な関数を使用することもできますSystem.Random。シード ( ) が正しくスレッド化されていることを確認する必要があるだけです(これを自動的に実行することが、基本的に発明されたStdGen理由です。それなしでプログラミングしようとした後、それをよりよく理解できるかもしれません!)State
  • 最後に、あなたはgetStdGen正しく使用していません。これはIOアクションなので、使用する前にその結果を -block にバインドする必要があり<-ますdo(技術的には、そうする必要はありません。多くのオプションがありますが、それはほぼ確実にやりたいことです)。このようなもの:

    do
      seed <- getStdGen
      results <- mapM (\game -> playGame game seed) [1..numberOfGames]
    

    ここplayGame :: Integer -> StdGen -> IO (String, Bool)に。ただし、同じランダム シードを eachplayGameに渡していることに注意してください。これは、必要な場合とそうでない場合があります。playGameそうでない場合は、シードを使い終わったときにそれぞれからシードを返して、次のシードに渡すか、新しいシードを繰り返し取得することができます (これを保持することにした場合newStdGenは、内部から行うことができます)。 )。playGameIO

とにかく、これは非常に構造化された回答ではありませんでした。申し訳ありませんが、考えるきっかけになれば幸いです。

于 2012-06-06T21:01:14.510 に答える