我正在关注此博客文章:http://semantic-domain.blogspot.com/2012/12/total-functional-programming-in-partial.html
它显示了System T (a simple total functional language)的小型OCaml编译器程序。
整个管道采用OCaml语法(通过Camlp4元编程)将它们转换为OCaml AST,然后将其转换为SystemT Lambda微积分(请参阅:module Term
),最后是SystemT组合器微积分(请参阅:
module Goedel
)。最后一步还包含OCaml元编程Ast.expr
类型。
我正在尝试不使用模板Haskell将其翻译为Haskell。
对于SystemT Combinator表单,我将其编写为
{-# LANGUAGE GADTs #-}
data TNat = Zero | Succ TNat
data THom a b where
Id :: THom a a
Unit :: THom a ()
ZeroH :: THom () TNat
SuccH :: THom TNat TNat
Compose :: THom a b -> THom b c -> THom a c
Pair :: THom a b -> THom a c -> THom a (b, c)
Fst :: THom (a, b) a
Snd :: THom (a, b) b
Curry :: THom (a, b) c -> THom a (b -> c)
Eval :: THom ((a -> b), a) b -- (A = B) * A -> B
Iter :: THom a b -> THom (a, b) b -> THom (a, TNat) b
请注意,Compose
是前向合成,与(.)
不同。
在将SystemT Lambda演算转换为SystemT组合器演算期间,Elaborate.synth
函数尝试将SystemT Lambda演算变量转换为一系列组成的投影表达式(与De Brujin指数有关)。这是因为组合器演算没有变量/变量名称。这是通过使用Elaborate.lookup
函数的Quote.find
完成的。
问题是我将组合器演算编码为GADT THom a b
。翻译Quote.find
函数:
let rec find x = function
| [] -> raise Not_found
| (x', t) :: ctx' when x = x' -> <:expr< Goedel.snd >>
| (x', t) :: ctx' -> <:expr< Goedel.compose Goedel.fst $find x ctx'$ >>
进入Haskell:
find :: TVar -> Context -> _
find tvar [] = error "Not Found"
find tvar ((tvar', ttype):ctx)
| tvar == tvar' = Snd
| otherwise = Compose Fst (find tvar ctx)
导致无限类型错误。
• Occurs check: cannot construct the infinite type: a ~ (a, c)
Expected type: THom (a, c) c
Actual type: THom ((a, c), c) c
问题源于使用Compose
GADT中的Fst
和Snd
和THom a b
可能导致类型签名无限变化的事实。
本文没有出现此问题,因为它似乎使用了Ast.expr
OCaml来包装基础表达式。
我不确定如何在Haskell中解决。我应该使用像这样存在的量化类型吗?
data TExpr = forall a b. TExpr (THom a b)
或者某种类型的Fix
来适应无限类型问题。但是,我不确定这如何改变find
或lookup
函数。
答案 0 :(得分:2)
这个答案必须有点概括,因为有三种完全不同的家族可能的设计来解决该问题。您正在做的事情似乎接近于三种方法,但是这些方法是按照越来越复杂的顺序排列的。
翻译原始帖子需要模板Haskell和局部性; find
将返回代表某个Q.Exp
的{{1}},避免了像原始帖子一样的问题。就像在原始帖子中一样,在对所有Template Haskell函数的输出进行类型检查时,将捕获原始代码中的类型错误。因此,仍然在运行前 捕获类型错误,但是您仍然需要编写 tests 来查找宏输出错误类型表达式的情况。一个人可以提供更强有力的保证,但代价是复杂性会大大增加。
如果您希望与该帖子有所不同,一种选择是在全文中使用“依赖类型”,并使 input 成为依赖类型。我宽松地使用该术语来包括实际的依赖类型的语言,实际的依赖Haskell(登陆时)以及当今在Haskell中伪造依赖类型的方式(通过GADT,单例以及其他方法)。 但是,您将无法编写自己的类型检查器并选择要使用的类型系统。通常,您最终会嵌入一个简单类型的lambda演算,并可以通过可生成给定类型项的多态Haskell函数来模拟多态。在依赖类型的语言中,这更容易,但是在Haskell中完全可以实现。
但是,老实说,在这条路中,更容易使用高阶抽象语法和Haskell函数,例如:
Hom a b
如果您可以使用这种方法(此处省略了详细信息),则可以从GADT(复杂度有限)中获得很高的保证。但是,这种方法在许多情况下太不灵活,例如,因为键入上下文仅对Haskell编译器可见,而对您的类型或术语不可见。
第三个选项是依赖类型的和,以使您的程序仍将弱类型的输入转换为强类型的输出。在这种情况下,您的类型检查器会将某种类型的data Exp a where
Abs :: (Exp a -> Exp b) -> Exp (a -> b)
App :: Exp (a -> b) -> Exp a -> Exp b
Var :: String -> Exp a —- only use for free variables
exampleId :: Exp (a -> a)
exampleId = Abs (\x -> x)
项转换为GADT Expr
,TExp gamma t
等项。由于您不知道Hom a b
和gamma
(或t
和a
)是什么,因此您确实需要某种存在性。
但是简单的存在性太弱了:要构建更大的类型正确的表达式,您需要检查产生的类型是否允许它。例如,要从两个较小的b
中构建一个包含TExpr
表达式的Compose
,则需要(在运行时)检查它们的类型是否匹配。有了简单的存在,您就不能做到。因此,您还需要在值级别上具有类型的表示形式。
(就像往常一样)还有更多烦人的存在,因为您永远无法在返回类型中公开隐藏类型变量,也无法将其投影(与从属记录/ sigma类型不同)。
老实说,我不完全确定这最终能否奏效。这是Haskell中可能的局部草图,最多TExpr
的一种情况。
find