假设我们有以下数据结构,其值代表某种语言的表达式:
data BExpr = Stop
| Guard Expr BExpr
| Choice BExpr BExpr
| Parallel BExpr BExpr
| Disable BExpr BExpr
-- ... and so on.
| Act Expr
data Expr = Var String | Val Int
我想定义一个函数substBExpr
,用BExpr
中的整数值替换变量名。这可以按如下方式完成:
subst :: Map String Int -> Expr -> Expr
subst substMap e@(Var name) = maybe e Val $ Map.lookup name substMap
subst _ v@(Val _) = v
substBExpr :: Map String Int -> BExpr -> BExpr
substBExpr _ Stop = Stop
substBExpr substMap (Guard gExp be) = Guard gExp' be'
where gExp' = subst substMap gExp
be' = substBExpr substMap be
substBExpr substMap (Choice be0 be1) = Choice be0' be1'
where be0' = substBExpr substMap be0
be1' = substBExpr substMap be1
substBExpr substMap (Parallel be0 be1) = Parallel be0' be1'
where be0' = substBExpr substMap be0
be1' = substBExpr substMap be1
substBExpr substMap (Disable be0 be1) = Disable be0' be1'
where be0' = substBExpr substMap be0
be1' = substBExpr substMap be1
substBExpr substMap (Act aExp) = Act aExp'
where aExp' = subst substMap aExp
问题是这需要很多样板,我想避免使用。
我现在可以想到两种解决方案。第一个解决方案是将类型参数添加到BExpr
,以便我可以为其自动派生Functor
的实例:
data GBExpr e = GStop
| GGuard e (GBExpr e)
| GChoice (GBExpr e) (GBExpr e)
| GParallel (GBExpr e) (GBExpr e)
| GDisable (GBExpr e) (GBExpr e)
-- ... and so on.
| GAct e
deriving (Show, Functor)
type BExpr2 = GBExpr Expr
substBExpr2 :: Map String Int -> BExpr2 -> BExpr2
substBExpr2 substMap = (subst substMap <$>)
虽然这有效,但如果Bexpr
无法更改为包含类型参数,则不可行。
第二个解决方案是使用gfoldl
:
substT :: (Typeable a) => Map String Int -> a -> a
substT substMap = mkT (subst substMap)
substExprs :: (Data a) => Map String Int -> a -> a
substExprs substMap a = runIdentity $ gfoldl step return (substT substMap a)
where
step :: Data d => Identity (d -> b) -> d -> Identity b
step cdb d = cdb <*> pure (substExprs substMap d)
其中涉及一些自己的样板(但我不知道遍历结构的任何函数,并且只有在它们可以转换为特定类型时才将函数应用于结构内的值)。
是否有更好的替代方案&#34;废弃你的样板&#34;问题? (我可以想象它可能是一些涉及镜头的解决方案,但我并不熟悉光学......)。