如何在基于recursion-schemes的表达式求值器中减少样板代码
完全懂你这种烦躁感!用recursion-scheme写表达式求值器的时候,那些只是转手传给运算符的x、y变量,纯纯的样板代码,看着就想删掉。下面给你几个实用的思路,帮你把这些冗余代码彻底干掉:
方法1:用uncurry+LambdaCase彻底消除变量绑定
这是我平时最常用的方案,开启LambdaCase扩展后,我们可以直接匹配二元构造子对应的运算符,再用uncurry自动处理参数传递,完全不用显式写x、y这类变量:
{-# LANGUAGE LambdaCase #-} import Data.Functor.Foldable -- 先定义你的表达式AST和对应的Base Functor data ExprF a = Lit Int | Add a a | Mul a a deriving (Functor, Foldable, Traversable) type Expr = Fix ExprF eval :: Expr -> Int eval = cata alg where alg :: ExprF Int -> Int alg (Lit n) = n -- 这里直接匹配构造子拿到运算符,uncurry自动处理参数 alg = uncurry . \case Add -> (+) Mul -> (*)
核心逻辑很简单:对于二元构造子(Add/Mul),我们先匹配构造子拿到对应的二元运算符,再用uncurry把运算符转换成能接收元组参数的形式,而recursion-scheme的cata会自动把递归求值后的结果传递进去,全程不需要手动绑定变量。
方法2:用ViewPatterns简化参数解构
如果你觉得上面的写法有点绕,开启ViewPatterns扩展后,可以把二元构造子的参数直接解构为元组,再用运算符处理——虽然还是要写参数名,但写法更直观:
{-# LANGUAGE ViewPatterns #-} import Data.Functor.Foldable eval :: Expr -> Int eval = cata $ \case Lit n -> n -- 直接把Add/Mul的参数解构为元组,省去单独绑定x、y的步骤 (Add -> (x, y)) -> x + y (Mul -> (x, y)) -> x * y
这种方式适合习惯直观模式匹配的场景,虽然没完全消除参数名,但比原来的样板写法清爽很多。
方法3:Template Haskell自动生成样板(适合复杂场景)
如果你的AST有非常多的构造子,手动写处理逻辑还是麻烦,可以用Template Haskell自动生成对应的折叠函数,彻底消除所有样板代码。比如你可以写一个TH宏,遍历你的ExprF构造子,自动生成对应的alg函数实现,完全不用手动写任何变量绑定。不过这种方案适合大型项目,小例子用前两种就足够了。
总的来说,第一种方法是最简洁高效的,完全聚焦于你真正关心的求值逻辑(运算符本身),把冗余的变量绑定彻底干掉。
内容的提问来源于stack exchange,提问作者Jogger
相关产品推荐
相关产品推荐

