如何使用generic-lens为类型编写Plated实例?
使用generic-lens实现Plated类型实例
当然可以用generic-lens库实现Plated类型实例,lens生态里也有对应的方案替代optics的gplate。
问题分析
你之前用types遇到的问题,是因为types是Biplate的泛型实现——当源类型s和目标类型a相同时,它会匹配整个根节点(表现等同于id),而Plated的plate需要仅遍历当前类型的直接子节点,不包含根节点本身。
解决方案:用genericTraversal实现
generic-lens提供了genericTraversal,它专门用于生成遍历同类型子节点的Traversal',完全符合Plated的需求。
完整代码示例
import Control.Lens.Plated (Plated(plate), everywhere) import Data.Generics.Traversal (genericTraversal) import GHC.Generics (Generic) -- 定义Expr类型并派生Generic data Expr = Lit Int | Add Expr Expr deriving (Generic, Show) -- 实现Plated实例 instance Plated Expr where plate = genericTraversal -- 测试:将所有Lit节点的值加1 addOne :: Expr -> Expr addOne = everywhere $ \case Lit n -> Lit (n + 1) e -> e -- 测试用例 main :: IO () main = print $ addOne (Add (Lit 1) (Add (Lit 2) (Lit 3))) -- 输出:Add (Lit 2) (Add (Lit 3) (Lit 4))
替代方案:手动构造Traversal
如果你不想依赖genericTraversal,也可以用generic-lens的构造器和类型遍历手动实现,更精细地控制子节点的遍历:
import Control.Lens.Plated (Plated(plate)) import Data.Generics.Product (types) import Data.Generics.Sum (_Ctor) import Control.Lens (Traversal', failing) import GHC.Generics (Generic) data Expr = Lit Int | Add Expr Expr deriving (Generic, Show) instance Plated Expr where plate = failing -- Lit没有子Expr节点,返回空遍历 (_Ctor @"Lit" . traverse) -- Add有两个Expr子节点,用types遍历它们 (_Ctor @"Add" . types @Expr)
额外说明
在lens生态中,还有专门的generic-plated包可以自动生成Plated实例,但如果你已经在使用generic-lens,上面的方案更贴合现有依赖栈。
内容的提问来源于stack exchange,提问作者Benjamin Hodgson
相关产品推荐
相关产品推荐

