我目前正在为一种编程语言开发一个简单的解释器,而我有一个类似于以下的数据类型:
data Expr
= Variable String
| Number Int
| Add [Expr]
| Sub Expr Expr
我有许多函数可以执行简单的任务,比如:
-- Substitute a value for a variable
substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue = go
where
go (Variable x)
| x == name = Number newValue
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
-- Replace subtraction with a constant with addition by a negative number
replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd = go
where
go (Sub x (Number y)) =
Add [go x, Number (-y)]
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
但在这些函数中,我必须重复调用代码的部分,并对函数的某个部分进行小的更改,以便实现递归。有没有更通用的方法来做到这一点?我不想复制和粘贴这一部分:
go (Add xs) =
Add $ map go xs
go (Sub x y) =
Sub (go x) (go y)
go other = other
每次只更改单个案例,因为复制这样的代码似乎效率低下。
我能想到的唯一解决方法是编写一个函数,在整个数据结构上首先调用一个函数,然后递归地在结果上调用该函数,代码如下:
recurseAfter :: (Expr -> Expr) -> Expr -> Expr
recurseAfter f x =
case f x of
Add xs ->
Add $ map (recurseAfter f) xs
Sub x y ->
Sub (recurseAfter f x) (recurseAfter f y)
other -> other
substituteName :: String -> Int -> Expr -> Expr
substituteName name newValue =
recurseAfter $ \case
Variable x
| x == name -> Number newValue
other -> other
replaceSubWithAdd :: Expr -> Expr
replaceSubWithAdd =
recurseAfter $ \case
Sub x (Number y) ->
Add [x, Number (-y)]
other -> other
但我觉得可能已经有更简单的方法了。我错过了什么吗?
Add :: Expr -> Expr -> Expr
而不是Add :: [Expr] -> Expr
,并且完全摆脱Sub
。 - chepnerrecurseAfter
是ana
的化身。你可能想要研究一下 ana 和recursion-schemes
。话虽如此,我认为你的最终解决方案已经非常简洁了。转向官方的recursion-schemes
anamorphisms 不会节省太多代码。 - chi