我想在Haskell中编写一个嵌入式DSL,从中我可以用另一种语言生成代码(即Python,但这与问题无关)。有很多不同的方法可以做到这一点,比如使用基于 Free monad 的解释器或我所知道的无标签解释器。但我所知道的方法似乎都不能捕获 * 函数定义 *,这是有道理的,但也是相当有限的。
我的问题是:如何将一个实际的Haskell函数嵌入到DSL中?这个想法是将一个Haskell函数定义 * 捕获 * 到类似Lam var body
构造函数的东西中,它看起来像:
data Var = Var ... -- use DeBruijn numbering?
data Expr repr = Lam Var repr
字符串
理想情况下,我希望能够写这样的东西:
foo :: Expr repr
foo = \ n -> n + n * 12
型
然后能够以各种方式解释这个Expr
,包括生成外来代码。
我尝试了https://hackage.haskell.org/package/data-reify,它提供了一些捕获函数的技术,但没有得到很远。我正在寻找的是可能的吗?
1条答案
按热度按时间krcsximq1#
我自己对Haskell还是个新手,然而,我最近自己也在做DSL,所以这是我的方法:
假设你有一个简单的AST,包括一个小的lambda演算:
字符串
请注意,上面的结构实际上不是类型安全的:不能确保绑定变量的类型与其单独使用的类型相同:
型
将编译没有问题。(注意内部变量与lambda表达式的参数具有不同的类型。)事实上,上述AST甚至不保证引用的变量被外部表达式绑定。
因为我们更喜欢使用Haskell函数类型,所以只要不将lambda演算导出到其他包就足够了。
现在让我们将一元函数“转换”为AST的表达式。我们想用
Var
占位符替换给定变量的所有占位符,然后用lambda绑定该变量:型
目前,我们还没有一种方法来确定一个独特的变量名,每当我们想要绑定一个变量。然而,我们需要这样做,以避免变量名冲突可靠。
一个简单的方法是在编译器中全局跟踪绑定的变量的数量,并根据该数量生成一个新的变量名。根据lambda表达式的使用/编译方式,由于作用域的原因,这可能不是必需的。
使用类型类,我们可以将其推广到n元函数的
convert
-函数:型
这已经很好了,但是会导致一些模棱两可的类型,因为我们将
c
声明为自由类型变量,所以理论上可以有多个转换函数类型的示例(本质上,c
不是由Exp a -> b
唯一确定的)。作为一种解决方案,你可以使用一个封闭类型族来确定
c
:型
例如:
型
现在我们可以从上面重写我们的类:
型
现在我们可以方便地转换AST的函数:
型
注1
上面的实现并没有覆盖我们AST表达式的 * 所有 * 函数,而是以下形式的函数
型
但是,可以添加更多类型的功能。
注2
根据DSL的实现/设计,最好直接从函数转换为生成语言的AST,跳过中间的lambda演算。这甚至可以帮助取消Haskell的函数,这些函数总是curry的。
注3
你必须启用一些扩展来运行代码。这些应该足够了:
型
我希望这能帮助你解决你所描述的问题。