如何折叠一个受歧视的联盟

时间:2012-09-05 19:05:52

标签: f# abstract-syntax-tree fold discriminated-union

我正试图对一个有区别的联盟实施折叠。 DU称为Expr,表示程序表达式,通常是递归的。我正在尝试编写一个折叠,以递归方式累积Exprs上的操作结果。以下是我尝试撰写文章。

let rec foldProceduralExpr (folder : 's -> Expr list -> 's) (state : 's) (expr : Expr) : 's =
    let children =
        match expr with
        | Series s -> s.SerExprs
        | Lambda l -> [l.LamBody; l.LamPre; l.LamPost]
        | Attempt a -> a.AttemptBody :: List.map (fun ab -> ab.ABBody) a.AttemptBranches
        | Let l -> l.LetBody :: List.concat (List.map (fun lb -> match lb with LetVariable (_, expr) -> [expr] | LetFunction (_, _, body, _, pre, post, _) -> [body; pre; post]) l.LetBindings)
        | Case c -> c.CaseTarget :: List.concat (List.map (fun branch -> [branch.TBBody; branch.TBTest]) c.CaseBranches)
        | Condition c -> List.concat (List.map (fun branch -> [branch.TBBody; branch.TBTest]) c.CondBranches)
        | List l -> l.ListElements
        | Array a -> Array.toList a.ArrElements
        | Composite c -> LunTrie.toValueList (LunTrie.map (fun _ mem -> mem.MemExpr) c.CompMembers)
        | _ -> []
    let listFolder = fun state expr -> foldProceduralExpr folder state expr
    let listFolded = List.fold listFolder state children
    folder state (expr :: listFolded)

问题是代码不起作用,这是众所周知的,因为我在最后一行的the construct causes code to be less generic than indicated by the type annotations. The type variable 's has been constrained to be type 'Expr list'上收到错误listFolded。这是因为foldProceduralExpr的定义几乎肯定是错误的。

现在,我想修复代码并因此修改类型错误,但我根本不知道如何修复。我想我缺乏的是对非列表或递归数据结构的折叠如何工作的理解。我最近翻译了一个从OCaml到F#的trie折叠,并且很难理解这个折叠是如何工作的。

问题1:这个代码是否有可见的解决方法,我可以理解为什么?

问题2:是否有资源可以获得理解如何编写这些类型折叠所需的背景知识?

如果您需要更多信息,请与我们联系。我没有把DU包括在内,因为它太大而且复杂,无法保持问题清晰。希望足以说明,这是典型的Lisp风格的AST数据结构。

跟进问题:一旦它起作用,我也想写一张带有折叠的地图。这看起来像一张典型的地图,还是需要一些额外的脑力才能弄清楚?

编辑:关于托马斯的帮助,我已将最后三行改为 -

let exprs = expr :: children
let listFolder = fun state expr -> foldProceduralExpr folder state expr
let listFolded = List.fold listFolder state exprs
folder listFolded exprs

我希望这仍然有意义。

编辑:这是我的最终解决方案。这是关于获得孩子的概括 -

let rec foldRdu (getChildren : 't -> 't list) (folder : 's -> 't -> 't list -> 's) (state : 's) (parent : 't) : 's =
    let children = getChildren parent
    let listFolder = fun state child -> foldRdu getChildren folder state child
    let listFolded = List.fold listFolder state children
    folder listFolded parent children

我将Expr和Expr列表作为折叠值的原因是我希望保留父/子关系的结构以便稍后使用。这是一个特定领域的折叠,我认为是这样。

2 个答案:

答案 0 :(得分:4)

第一个问题是,您想要实施什么样的折叠?树状结构有很多选择。假设你有一个 n-ary 树(即任意数量的孩子),你可以:

  1. 仅在叶子中的Expr个节点上应用折叠功能
  2. 在叶子上应用折叠功能,然后将其应用于所有内部节点(递归调用后)
  3. 在处理树时,在内部节点上应用折叠功能,然后在叶子上
  4. 在你的例子中,你的文件夹功能需要Expr list这对我来说有点混乱 - 我认为你可以给它当前的Expr而不是一个孩子的列表,但是调用它孩子也是一种选择。

    你首先进行递归调用,然后在当前表达式上调用该文件夹,所以我想你想要实现选项2.我可以理解折叠的前两行:

    // This defines a function that recursively folds the a given 
    // expression and produces a new state of type 's
    let listFolder = fun state expr -> foldProceduralExpr folder state expr 
    // Here, you fold all children recursively starting with 'state' and 
    // obtaining a result 'listFolded' of type 's
    let listFolded = List.fold listFolder state children 
    

    然而,最后一行可能是错误的:

    // From the previous line, 'listFolded' is of type 's, but 
    // here you use it as if it was a list:
    folder state (expr :: listFolded) 
    

    我假设您可能希望使用listFolded作为传递给folder的状态。该文件夹采用表达式列表,因此您可以将其children作为第二个参数(或者您可以将folder更改为只使用一个Expr,然后只提供{{1}恕我直言,这将更有意义)。无论如何,这应该使它工作,以便你可以运行它,看看发生了什么:

    expr

答案 1 :(得分:2)

我曾写过一篇关于维护涉及大型歧视联盟及其变换的代码的实用方法的文章:

http://t0yv0.blogspot.com/2011/09/transforming-large-unions-in-f.html

在实践中,我发现折叠假定太多,经常有折叠无法表达的转换。您还需要控制转换的方式 - 自下而上或自上而下,以及特别跳过或处理哪些节点。本文将介绍如何使用简单的transform函数执行此操作。我还从transform开始只有几行。

它没有直接回答如何解决您的问题,但它可能会对构建此类代码的另一种方式有所启发。