这个问题本质上与parsing for PHOAS expressions相同,但是我特别寻找一个使用Maybe
类型安全处理范围外变量的答案(因此不使用Haskell的错误机制)。
具体来说,我有这些类型:
type Name = Int
data Lam = Var Name | Lam Name Lam | App Lam Lam
data LamP p = VarP p | LamP (p -> LamP p) | AppP (LamP p) (LamP p)
为了简单起见,我使用整数作为名称。
从LamP
到Lam
的转换非常简单:
fromP :: (forall p. LamP p) -> Lam
fromP x0 = go 0 x0 where
go _ (VarP n) = Var n
go n (LamP f) = Lam n (go (n + 1) (f n))
go n (AppP x y) = App (go n x) (go n y)
下面是我的另一个尝试:
type Env p = Int -> Maybe p
emptyEnv :: Env p
emptyEnv = const Nothing
lookupEnv :: Env p -> Int -> Maybe p
lookupEnv = id
insertEnv :: Env p -> Int -> p -> Env p
insertEnv f n x m
| n == m = Just x
| otherwise = f m
toP :: Lam -> Maybe (LamP p)
toP x0 = go x0 emptyEnv where
go :: Lam -> Env p -> Maybe (LamP p)
go (Var n) env = VarP <$> lookupEnv env n
go (Lam n x) env =
go x (insertEnv env n _) >>= \x' -> pure (LamP (\p -> _) -- <<< problem!
go (App x y) env = AppP <$> go x env <*> go y env
似乎不可能将p
从LamP
内部移动到其左侧的insertEnv
。
然而,我坚信go
计算的Just/Nothing
结果不应该取决于我们在环境中插入了哪个p
,所以我想放一个占位符,然后再填充它,但是我不知道该怎么做。
还是无法编写此函数?
1条答案
按热度按时间5f0d552i1#
这里有一个(部分不完整的)解决方案。
其思想是将GADT作为中间表示进行传递。
在FOAS中,我们使用De Bruijn指数和Peano自然数来表示变量。
现在我们用GADT来表示一个自由变量在
[0,n)
中的项,这些项有一个特殊的WeakC
语法来进行显式弱化,这样我们就可以考虑一个自由变量在[0,n)
中的项,就像在[0,n+1)
中一样。注意
VarC
是如何“被最后一个lambda绑定的变量”的,即De Bruijn索引为零的变量。如果我们想访问其他变量,我们需要使用WeakC
。还要注意,应用程序如何方便地要求两边都有相同的
n
--这并没有失去推广性,因为我们有弱化,我们可以根据需要使这些索引为相同的n
。现在,将 closed
Lam
项转换为LamC 'O
看起来是可行的。我跳过了这一部分,将其作为练习。这可能需要使用一些Nat
单例和存在体,因为这需要推广到... -> Maybe (exists n . (SNat n, LamC n))
以使其归纳。看起来需要在应用程序中执行弱化操作。不过,您应该能够实际编写此代码。最后,PHOAS术语:
环境现在是一个GADT,提供
n
变量的值。现在我们可以把
LamC
项转换成LamP
,只要我们有一个合适的环境,这样就可以很好地递归。请注意,这里我们不必返回
Maybe (LamP p))
,这就避免了您在使用Nothing
时发现的问题。这里,我们确信环境提供了p
,而不是Maybe p
,因此一切都是完整的(由于我们的GADT),我们不再需要担心。最后,我们把碎片粘在一起。