我对Haskell很新,我有一个评估,涉及布尔表达式的操纵器和评估器。
Thee表达式是:
type Variable = String
data Expr = T | Var Variable | And Expr Expr | Not Expr
我已经解决了很多问题,但我仍然坚持如何处理以下功能。我需要计算表达式
中所有变量的出现次数addCounter :: Expr -> Expr
addCounter = undefined
prop_addCounter1 = addCounter (And (Var "y") (And (Var "x") (Var "y"))) ==
And (Var "y1") (And (Var "x2") (Var "y1"))
prop_addCounter2 = addCounter (Not (And (Var "y") T)) ==
Not (And (Var "y1") T)
我不是在寻找一个关于如何做到这一点的答案,因为这是一个评估问题,但我想要一些关于如何接近这个的提示?
在我的脑海中,我想到递增一个计数器,这样我就可以获得y1
,x2
部分,但这并不是Haskell中可能实现的(或者不建议做任何事情!)我会通过递归来解决这个问题,如果是这样,我怎么知道要添加到变量的数字?
答案 0 :(得分:2)
正如你所说,在这种情况下你不能保留一个非常自然的共享计数器。您可以做的是在递归访问所有Expr时将当前计数器值传递到树中,并从被调用的函数接收递增的计数器值。 必须是双向沟通。您传递当前值并接收更新的Expr和新计数器值。
如果希望每个唯一变量名具有相同的计数器值,则需要将变量名称映射到指定的计数器值。您需要像当前计数器值一样传递那个。
希望有所帮助。
答案 1 :(得分:1)
所以,这绝对是使用State
monad的好时机。特别是,您正在寻找的原子变换是一种通过每个字符串的唯一ID枚举字符串String -> String
的方法。我们称之为enumerate
import Control.Monad.State
-- | This is the only function which is going to touch our 'Variable's
enumerate :: Variable -> State OurState Variable
为此,我们需要跟踪将String
映射到计数(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 :: 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
类型的叶子上应用枚举来映射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
)上应用monadic变换。
现在,我们只需解压State
个动作以制作addCounter
addCounter :: Expr -> Expr
addCounter = runOurState . onVarStringM enumerate
注意,这实际上没有正确的行为 - 它没有很好地枚举你的变量(prop_addCounter1
失败但prop_addCounter2
通过)。不幸的是,我不确定应该怎么做......但鉴于这里存在的问题分离,只需编写适当的enumerate
State
个完整行动就可以了。将它应用于相同的通用Expr
- 转换机制。