所以我想要在节点类型为
的地方折叠一棵树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)
答案 0 :(得分:4)
执行所需操作的直接方法是将validate
的特殊情况编写为:
validate env expr = concat $ gmapQ ([] `mkQ` (validate env)) expr
这使用mkQ
中的Data.Generics.Aliases
。 mkQ
的重点是创建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