自动将函数应用于子结构

时间:2012-02-08 18:42:25

标签: haskell recursion algebraic-data-types

假设我在抽象语法树数据类型上编写“替换”函数:

data Term = Id String
          | If Term Term Term
          | Let String Term Term
          ...

subst :: String -- name of identifier to replace
      -> Term   -- term to replace the identifier with
      -> Term   -- body in which to perform the replacements
      -> Term
subst identifier term = go
  where go (Id id') = if identifier == id' then term else Id id'
        go (If t1 t2 t3) = If (go t1) (go t2) (go t3)
        go (Let id' term' body) = Let id' (go term') (go body)
        ...

(忽略阴影问题)。注意编写If分支是多么乏味。我必须模式匹配,命名3个部分,然后明确地重构If应用go到3个部分中的每个部分。对于Let,我必须进行模式匹配,命名3个部分,然后明确重建Letgo应用于相关的2个部分。是否有一种更容易(无点?)的方式来写这个而不必拼出每一个细节?

1 个答案:

答案 0 :(得分:13)

这里最好的方法是使用datatype-generic编程,这是为这样的AST行走任务精确设计的。这是一个使用标准SYB库的示例:

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Generics

data Term = Id String
          | If Term Term Term
          | Let String Term Term
          deriving (Eq, Show, Typeable, Data)

subst :: String -- name of identifier to replace
      -> Term   -- term to replace the identifier with
      -> Term   -- body in which to perform the replacements
      -> Term
subst identifier term = everywhere (mkT go)
  where go (Id id') = if identifier == id' then term else Id id'
        go x = x

这直接表示转换(在这种情况下,将函数go应用于Term类型的任何子项)应该以自下而上的方式应用于整个树。这不仅更简洁,而且只要基本递归方案保持不变,无论向Term添加多少构造,相同的代码都会继续工作。