【发布时间】:2020-02-14 18:12:20
【问题描述】:
我目前正在为一种编程语言开发一个简单的解释器,我有一个这样的数据类型:
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
而且每次只更改一个 case,因为像这样复制代码似乎效率低下。
我能想出的唯一解决方案是有一个函数,它首先在整个数据结构上调用一个函数,然后递归地调用这样的结果:
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。 -
我只是把这个定义当作一个简化的版本;虽然这在这种情况下可行,但我还需要能够包含该语言其他部分的表达式列表
-
比如?大多数(如果不是全部)链式运算符可以简化为嵌套的二元运算符。
-
我认为你的
recurseAfter是伪装的ana。你可能想看看变形和recursion-schemes。话虽如此,我认为您的最终解决方案尽可能短。切换到官方的recursion-schemes变形不会节省太多。
标签: haskell functional-programming dry code-duplication recursive-type