纯函数编译器如何使用类型信息注释AST?

时间:2015-04-12 09:55:23

标签: scala haskell compiler-construction f# functional-programming

在语法分析阶段,命令式编译器可以从已经包含type字段的节点构建AST,该字段在构造期间设置为null,然后在语义分析阶段,通过将声明/推断类型分配到type字段来填写类型。

纯粹的功能语言如何处理这一问题,而你却没有分配的奢侈品?无类型AST是否映射到类型丰富的AST的不同?这是否意味着我需要为每个AST节点定义两种类型,一种用于语法阶段,另一种用于语义阶段?

是否有纯函数式编程技巧可以帮助编译器编写这个问题?

3 个答案:

答案 0 :(得分:4)

有几种方法可以对此进行建模。您可以使用与命令式案例中相同类型的可空数据字段:

data Exp = Var Name (Maybe Type) | ...
parse :: String -> Maybe Exp     -- types are Nothings here
typeCheck :: Exp -> Maybe Exp    -- turns Nothings into Justs

甚至使用更精确的类型

data Exp ty = Var Name ty | ...
parse :: String -> Maybe (Exp ())
typeCheck :: Exp () -> Maybe (Exp Type)

答案 1 :(得分:3)

我无法说明假设是如何完成的,但我确实在F#中为C#编译器执行了此操作here

这个方法基本上是 - 从源代码构建一个AST,留下类型信息不受约束的东西 - 所以AST.fs基本上是AST,它为类型名称,函数名称等字符串。

当AST开始被编译为(在这种情况下).NET IL时,我们最终会得到更多的类型信息(我们在源代码中创建类型 - 让我们调用这些类型的存根)。然后,这为我们提供了创建方法存根所需的信息(代码可能包含包含类型存根和内置类型的签名)。从这里开始,我们现在有足够的类型信息来解析代码中的任何类型名称或方法签名。

我将其存储在TypedAST.fs文件中。我一次性完成这项工作,但这种做法可能很天真。

现在我们有一个完全类型化的AST,你可以做一些事情,比如编译它,完全分析它,或者你喜欢用它做什么。

所以回答问题“这是否意味着我需要为每个AST节点定义两种类型,一种用于语法阶段,一种用于语义阶段?”,我无法明确地说情况确实如此,但这肯定是我所做的,而且它似乎是MS对Roslyn的所作所为(尽管它们基本上用原型树IIII来装饰原始树)

是否有纯函数式编程技巧可以帮助编译器编写这个问题?” 鉴于AST在我的情况下基本上是镜像的,它可以使它成为通用的并转换树,但代码可能最终(更多)可怕。

type 'type AST;
| MethodInvoke of 'type * Name * 'type list
| ....

答案 2 :(得分:0)

与处理关系数据库的情况一样,在函数式编程中,通常不要将所有内容都放在单个数据结构中。

特别是,可能没有“AST”的数据结构。

最有可能的是,会有表示已解析表达式的数据结构。处理类型信息的一种可能方式是在解析期间已经为树的每个节点分配唯一标识符(如整数),并且具有将这些节点ID与类型相关联的一些合适的数据结构(如哈希映射)。那么,类型推理传递的工作就是创建这个地图。