You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:31:21