ステートフルな更新を霧化する
State
したがって、これは間違いなくモナドを使用する絶好の機会です。特に、探しているアトミック変換は、String -> String
文字列ごとに一意の ID で文字列を列挙する方法です。呼びましょうenumerate
import Control.Monad.State
-- | This is the only function which is going to touch our 'Variable's
enumerate :: Variable -> State OurState Variable
これを行うには、String
s をカウント ( Int
s)にマップする状態を追跡する必要があります。
import qualified Data.Map as M
type OurState = Map String Int
runOurState :: State OurState a -> a
runOurState = flip evalState M.empty
runOurState $ mapM enumerate ["x", "y", "z", "x" ,"x", "x", "y"]
-- ["x1", "y1", "z1", "x2", "x3", "x4", "y2"]
そのため、enumerate をかなり直接的にステートフル アクションとして実装できます。
enumerate :: Variable -> State OurState Variable
enumerate var = do m <- get
let n = 1 + M.findWithDefault 0 var m
put $ M.insert var n m
return $ var ++ show n
涼しい!
一般的な式ツリーの折りたたみ
ここで、各タイプの葉Expr -> State OurState Expr
に enumerate を適用してマッピングする精巧な折り畳み装置を実際に作成する必要があります。Var
enumerateExpr :: Expr -> State OurState Expr
enumerateExpr T = return T
enumerateExpr (Var s) = fmap Var (enumerate s)
enumerateExpr (And e1 e2) = do em1 <- addCounter e1
em2 <- addCounter e2
return (Add em1 em2)
enumerateExpr (Not expr) = fmap Not (addCounter expr)
しかし、これはかなり面倒なので、Uniplate
ライブラリを使用してドライに保ちます。
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Data
import Data.Generics.Uniplate.Data
data Expr = T | Var Variable | And Expr Expr | Not Expr
deriving (Show,Eq,Ord,Data)
onVarStringM :: (Variable -> State OurState Variable) -> Expr -> State OurState Expr
onVarStringM action = transformM go
where go :: Expr -> State OurState Expr
go (Var s) = fmap Var (action s)
go x = return x
このtransformM
演算子は、まさに私たちが望むことを行います — ジェネリック ツリー (私たちのExpr
) のすべての部分にモナド変換を適用します。
これで、State
作成するフル アクションを展開するだけです。addCounter
addCounter :: Expr -> Expr
addCounter = runOurState . onVarStringM enumerate
あっ、待って!
気づいたのですが、これは実際には正しい動作をしていません。変数を正しく列挙していません (prop_addCounter1
失敗するがprop_addCounter2
成功する)。残念ながら、それがどのように行われるべきかはよくわかりません... しかし、ここで説明されているこの懸念の分離を考えると、適切なenumerate
State
ful アクションを記述し、それを同じ一般的なExpr
変換機構に適用するのは非常に簡単です。