原子化你的有状态更新
所以,这绝对是使用State
monad 的好时机。特别是,您正在寻找的原子转换是一种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
凉爽的!
一般折叠在表达式树上
现在我们真的应该编写一个精细的折叠设备,通过在每个-type 叶子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
机器就很容易了。