5

IO モナドと一緒に AppState を構築するために、状態と IO アクションを組み合わせるで与えられたアドバイスに従おうとしています。私が得たのはこれです:

module Main where

import Control.Monad.State
import Control.Monad.Trans

data ST = ST [Integer] deriving (Show)
type AppState = StateT ST IO

new = ST []

append :: Integer -> State ST ()
append v = state $ \(ST lst) -> ((), ST (lst ++ [v]))

sumST :: State ST Integer
sumST = state $ \(ST lst) -> (sum lst, ST lst)

script = do
    append 5
    append 10
    append 15
    sumST

myMain :: AppState ()
myMain = do
    liftIO $ putStrLn "myMain start"
    let (res, st) = runState script new
    liftIO $ putStrLn $ show res
    liftIO $ putStrLn "myMain stop"

main = runStateT myMain (ST [15])

これのいくつかの部分が得られていません。script私がとmyMain を持っていることは非常に気になりmainます。runStateまた、内部で実行する必要があり、メイン関数にmyMain初期状態をフィードする必要があることも気になります。runStateTmyMain の全体的なポイントは、追加と合計を myMain で直接、印刷操作のすぐ隣で実行できるようにすることであるため、いわば「スクリプト」を myMain 関数で直接使用したいと考えています。代わりに、これを行うことができるはずだと思います:

myMain :: AppState ()
myMain = do
    liftIO $ putStrLn "myMain start"
    append 5
    append 10
    append 15
    r <- sumST
    liftIO $ putStrLn $ show res
    liftIO $ putStrLn "myMain stop"

main = runState myMain

モナドトランスフォーマーのポイントは、State モナド操作を関数 (上記のように) で実行し、IO 操作をその関数に持ち上げることだと考えていました。間接的なレイヤーの 1 つを削除できるように、これらすべてを設定する正しい方法は何ですか?


ダニエルの解決策 (私は解決策にフラグを立てました) に加えて、状況に光を当てる可能性のあるいくつかのバリエーションも見つけました。まず、myMain と main の最終的な実装:

myMain :: AppState ()
myMain = do
    liftIO $ putStrLn "myMain start"
    append 5
    append 10
    append 15
    res <- sumST
    liftIO $ putStrLn $ show res
    liftIO $ putStrLn "myMain stop"

main = runStateT myMain new

現在、ダニエルの実装に加えて、append と sumST のさまざまな実装:

append :: Integer -> AppState ()
append v = state $ \(ST lst) -> ((), ST (lst ++ [v]))

sumST :: AppState Integer
sumST = state $ \(ST lst) -> (sum lst, ST lst)

および (型宣言のみが変更されることに注意してください。実際、型宣言を完全に省略できます!)

append :: MonadState ST m => Integer -> m ()
append v = state $ \(ST lst) -> ((), ST (lst ++ [v]))

sumST :: MonadState ST m => m Integer
sumST = state $ \(ST lst) -> (sum lst, ST lst)

AppState/StateT モナドが基本的な State モナドと同じではないことに気がつき、State モナドに対して sumST と append の両方をコーディングしていました。ある意味では、これらは StateT モナドに持ち上げる必要もありましたが、 in の正しい考え方は、モナドで実行する必要があるということです(したがって、runState script new)。

完全に理解しているかどうかはわかりませんが、しばらく作業して、MonadState コードを読み、最終的に頭の中で動作するようになったら、これについて何かを書きます。

4

1 に答える 1

10

問題は、appendandsumST関数を単形化しすぎたことです! 関数を直接使用する代わりにstate、より多態getputな関数を使用して、よりエキサイティングな型を与えることができます。

append :: MonadState ST m => Integer -> m ()
append v = do
    ST lst <- get
    put (ST (lst ++ [v]))

sumST :: MonadState ST m => m Integer
sumST = do
    ST lst <- get
    return (sum lst)

次に、myMain提案したとおりに正確に記述できます (ただし、初期状態を に指定する必要がありますmain)。

スタイル上の理由から、新しい型を定義しないことをお勧めSTします。リストで便利なことを行う関数はたくさんありますが、リストとSTリストの間にコンストラクターを挿入してそれらを使用できなくするのは面倒です! 代わりに状態タイプとして使用する場合[Integer]、次のような定義を作成できます。

prepend :: MonadState [Integer] m => Integer -> m ()
prepend = modify . (:)

sumST :: MonadState [Integer] m => m Integer
sumST = gets sum

かなりいいですね。=)

于 2012-07-06T13:27:28.503 に答える