如何折叠具有特殊情况的构造函数?

时间:2017-10-05 17:22:39

标签: haskell generics scrap-your-boilerplate

所以我想要在节点类型为

的地方折叠一棵树
data Node = Node1 Node | Node2 Node Node | ... deriving Data

除少数特殊情况外。我想按照

的方式做点什么
collapse SPECIALCASE1 = ...
collapse SPECIALCASE2 = ...
...
collapse node = foldl (++) $ gmapQ validate node

其中所有特殊情况都生成结果列表,最后一个案例只是递归地折叠;但这不起作用,因为作为gmapQ的第一个参数的函数必须是forall d. Data d => d -> u类型而不是Node -> u,据我所知只是限制你只使用操作函数在Data类型。

是否有任何方法可以将问题中的值强制转换为正确的类型,或者是另一种更宽松的地图函数呢?

额外信息:

上面描述为collapse的函数的实际代码名为validate,用于遍历和查找抽象语法树中的未绑定变量(对于非常简单的语言),特殊情况是像这样处理

validate _ (Nr _) = []
validate env (Let var val expr) = validate env val ++ validate (var:env) expr
validate env (Var var) = if elem var env then [] else [var]

本质上是文字数字中没有变量的规则,让表达式绑定变量,如果绑定或不绑定,则需要检查变量。这种玩具语言中的每个其他构造只是数字和变量的组合(例如求和,乘法等),因此当我检查未绑定的变量时,我只需要遍历它们的子树并组合结果;因此gmapQ

额外信息2:

使用的实际数据类型代替上面的Node示例,格式为

data Ast = Nr Int
         | Sum Ast Ast
         | Mul Ast Ast
         | Min Ast
         | If Ast Ast Ast
         | Let String Ast Ast
         | Var String
           deriving (Show, Eq, Data)

2 个答案:

答案 0 :(得分:4)

执行所需操作的直接方法是将validate的特殊情况编写为:

validate env expr = concat $ gmapQ ([] `mkQ` (validate env)) expr

这使用mkQ中的Data.Generics.AliasesmkQ的重点是创建forall d. Data d => d -> u类型的查询,这些查询可以在不同的Data实例上以不同的方式运行。顺便说一句,这里没有魔力。您可以使用cast手动将其定义为:

validate env expr = concat $ gmapQ myQuery expr
  where myQuery :: Data d => d -> [String]
        myQuery d = case cast d of Just d -> validate env d
                                   _ -> []

尽管如此,我一般发现使用uniplate库中的lens更加清晰。我们的想法是创建一个默认的Plated实例:

instance Plated Ast where
  plate = uniplate   -- uniplate from Data.Data.Lens 

它神奇地定义children :: Ast -> [Ast]以返回节点的所有直接后代。然后,您可以将默认validate案例写为:

validate env expr = concatMap (validate env) (children expr)

完整代码w /打印[“z”]的测试:

{-# LANGUAGE DeriveDataTypeable #-}

module SpecialCase where

import Control.Lens.Plated
import Data.Data
import Data.Data.Lens (uniplate)

data Ast = Nr Int
         | Sum Ast Ast
         | Mul Ast Ast
         | Min Ast
         | If Ast Ast Ast
         | Let String Ast Ast
         | Var String
           deriving (Show, Eq, Data)

instance Plated Ast where
  plate = uniplate

validate env (Let var val expr) = validate env val ++ validate (var:env) expr
validate env (Var var) = if elem var env then [] else [var]
-- either use this uniplate version:
validate env expr = concatMap (validate env) (children expr)
-- or use the alternative, lens-free version:
-- validate env expr = concat $ gmapQ ([] `mkQ` (validate env)) expr

main = print $ validate [] (Let "x" (Nr 3) (Let "y" (Var "x") 
             (Sum (Mul (Var "x") (Var "z")) (Var "y"))))

答案 1 :(得分:1)

对不起,我太慢了,无法在K. A. Buhr之前写出基于Data的答案。这是另一种方法,基于recursion-schemes

首先,样板:

{-# LANGUAGE TemplateHaskell, TypeFamilies
           , DeriveTraversable #-}

import Data.Functor.Foldable
import Data.Functor.Foldable.TH

data Ast = Nr Int
         | Sum Ast Ast
         | Mul Ast Ast
         | Min Ast
         | If Ast Ast Ast
         | Let String Ast Ast
         | Var String
         deriving (Show, Eq)

makeBaseFunctor ''Ast

这将创建一个AstF类型,从Ast中取出递归。它看起来像这样:

data AstF ast = NrF Int
              | SumF ast ast
              | MulF ast ast
              ....
              deriving (Functor,Foldable,Traversable)

然后它还创建了几个实例。我们将使用两个自动生成的实例:Recursive Ast实例以递归方式验证树,Foldable实例AstF实例连接结果来自默认情况下的孩子。

我发现为环境创建单独的类型很有帮助;这是非常可选的。

newtype Env = Env {getEnv :: [String]}

emptyEnv :: Env
emptyEnv = Env []

extendEnv :: String -> Env -> Env
extendEnv a (Env as) = Env (a : as)

isFree :: String -> Env -> Bool
isFree a (Env as) = not (elem a as)

现在,我们可以开始使用Recursive Ast实例免费获取cata

validate :: Env -> Ast -> [String]
validate env0 ast0 = cata go ast0 env0
  where
    go :: AstF (Env -> [String]) -> Env -> [String]
    go (LetF var val expr) env = val env ++ expr (extendEnv var env)
    go (VarF var) env = [var | isFree var env]
    go expr env = foldMap id expr env