在尝试判断EDSL对我的项目是否谨慎时,我读了this paper和this paper,描述了meta-repa的实现。他们都提到了HOAS和FOAS。从第一篇论文开始,
data FunC a where LitI :: Int -> FunC Int LitB :: Bool -> FunC Bool If :: FunC Bool -> FunC a -> FunC a -> FunC a While :: (FunC s -> s -> FunC Bool) -> (FunC s -> FunC s) -> FunC s -> FunC s Pair :: FunC a -> FunC b -> FunC (a, b) Fst :: FunC (a, b) -> FunC a Snd :: FunC (a, b) -> FunC b Prim1 :: String -> (a -> b) -> FunC a -> FunC b Prim2 :: String -> (a -> b -> c) -> FunC a -> FunC b -> FunC c Value :: a -> FunC a Variable :: String -> FunC a
我们还选择了高阶抽象语法来表示 具有变量绑定的构造。在上面的数据类型中,唯一的 高阶构造是
While
。
While
构造函数如何使它成为HOAS?为什么没有其他构造函数HOAS?
在第二篇论文中,meta-repa code写在HOAS树中,然后(在编译时)转换为FOAS进行进一步处理。同样,我不了解在HOAS.hs HOAS中定义数据的原因,而FOASTyped中定义的数据是FOAS。该论文的神秘引言是:
类型
Expr
[在HOAS.hs中]使用更高阶的抽象语法来表示程序。 这种表示方便编程,但有点 不太适合重写程序。因此AST被转换成 第一顺序表示[。] A. 可能的实现是跳过[HOAS]Expr
类型和 直接生成第一个订单表示。我们保留了 高阶表示部分是因为它有助于保持类型 实施的安全性,部分原因是它允许我们写作 一个好的,无标记的翻译。
HOAS比FOAS更难以改造吗?与FOAS相比,HOAS如何帮助确保类型安全?
我已经阅读了有关FOAS和HOAS的维基百科文章,但这对我来说并不清楚。
维基百科建议HOAS在具有可变绑定器的语言中很有用(在第一个引用中也有提及)。什么是变量绑定器,Haskell如何实现它,以及哪些语言没有可变绑定器?
答案 0 :(得分:12)
在FOAS中,我们用标识符表示变量,所以
data STLC = Var String
| Lam String STLC
| Unit
| STLC :*: STLC
term = Lam "a" $
Lam "b" $
Var "a" :*: (Lam "a" $ Var "a")
我们有明确的变量,现在由我们来确保范围和变量绑定正常工作。额外的工作有它的奖励,因为我们现在可以检查和模式匹配lambda的身体,这对大多数转型至关重要。
HOAS本质上是我们使用宿主语言(Haskell)实现的变量,而不是在AST中表示它们。
例如,考虑STLC
data STLC = Unit
| Lam (STLC -> STLC)
| STLC :*: STLC
注意我们如何使用Haskell函数STLC -> STLC
来表示由lambda绑定的变量。这意味着我们可以写
term = Lam $ \a ->
Lam $ \b ->
a :*: (Lam $ \a -> a)
它有效。在正常的AST中,我们必须确保我们正确地对所有内容进行alpha转换,以确保我们正确地遵守范围。同样的优势适用于绑定变量(变量绑定器)的所有事情:让表达式,连续性,异常处理程序,等等。
这有一个主要的缺点,因为Lam
具有完全抽象的功能,我们根本无法检查函数的主体。这使得很多转换很好,很痛苦,因为所有内容都被Haskell绑定包裹起来。
另一个好处是,由于我们没有为变量提供显式构造函数,所以所有术语都保证关闭。
通常这意味着我们用HOAS和FOAS的组合来代表事物。
答案 1 :(得分:5)
While构造函数如何使它成为HOAS?
让我们看一下While
构造函数的第二个参数:While :: ... -> (FunC s -> FunC s) -> ...
。在此字段的类型中,FunC
显示在箭头的左侧。因此,如果在While
程序中使用FunC
,则程序不是内存中的抽象语法树,而是更复杂的语法树。 FunC s -> FunC s
的预期含义是“FunC s
具有s
类型的自由变量”。我想这用于while循环的主体,而free变量包含在每次循环迭代中发生变化的值。
为什么其他构造函数都没有HOAS?
他们没有我们在上面的... -> (FunC ... -> ...) -> ...
构造函数中看到的While
模式。因此,如果FunC
值仅使用其他构造函数,则其内存表示看起来像抽象语法树。
同样,我不明白在HOAS.hs HOAS中定义数据的原因是什么,而FOASTyped中定义的数据是FOAS。
您可以查看论文中代码的FOAS版本,了解它们如何更改While
的类型以避免HOAS模式,以及需要更改以使其工作的其他内容。
HOAS比FOAS更难以改造吗?
HOAS程序不是树,因此您无法对其进行模式匹配。例如,你不能在While (\_ _ (LitB False)) ...
上进行模式匹配,因为你无法在这样的lambda上匹配。
与FOAS相比,HOAS如何帮助确保类型安全?
在HOAS程序中,您使用Haskell变量来表示FunC
变量。 Haskell类型检查器将检查您是否仅在相应的变量绑定范围内使用Haskell变量。 (GHC告诉你“不在范围内:foo'
”否则)。由于FunC
变量表示为Haskell变量,因此此检查对FunC
的类型安全性也很有用。如果你使用HOAS编码的FunC
变量超出范围,Haskell类型检查器会抱怨Haskell变量超出范围。
现在在FOAS中,如果你使用Haskell字符串作为FunC变量,如果使用错误的字符串,Haskell类型检查器将永远不会抱怨,因为就GHC而言,你可以使用你想要的任何字符串。有一些技术可以改进FOAS以使Haskell类型检查器检查您的嵌入式程序,但它们往往需要嵌入式语言用户的更多工作。
什么是变量活页夹?
变量绑定器是一种语言结构,它引入了可以在程序的其他部分中使用的新名称。例如,在Haskell中,如果我写let x = 14 in ...
,我会引入一个新名称x
,我可以在...
中使用。 Haskell中的其他绑定器包括lambda表达式,模式匹配和顶级定义。
Haskell如何实现它?
我真的不明白这个问题。对于类型检查,GHC会跟踪哪些变量在范围内,如果在错误的位置使用变量,则会抱怨。对于编译,GHC生成的机器代码“知道”变量表示的值的位置,通常是因为指向变量值的指针存储在处理器寄存器或堆栈或堆中。
哪些语言没有可变绑定器?
许多小型专业语言没有可变绑定器。
例如,考虑正则表达式。至少最初,他们无法绑定变量。 (一些正则表达式引擎使用反向引用,但这是一种变量形式。)
另一个例子是网址的“语言”。 URL由各种部分(协议,服务器名称,路径,参数......)组成,其中包含有关您可以写和不可写的内容的规则,因此它是一种语言。但是,您无法在URL中引入名称,以后可以在URL中使用该名称。
许多低级语言没有可变绑定器。
有图灵完整的语言,没有可变的绑定器。