使用无类型lambda演算进行冒险

时间:2015-07-10 18:46:14

标签: haskell types lambda-calculus

我们偶尔会有人询问在Haskell中实现无类型的lambda演算。 [当然,我现在不能找到任何这些问题,但我确定我已经看过他们了!]只是为了咯咯笑,我以为我会花一些时间玩有这个。

做一些像

这样的事情是微不足道的
i = \ x -> x
k = \ x y -> x
s = \ f g x -> (f x) (g x)

这完美无缺。但是,只要您尝试执行类似

的操作
s i i

类型检查器正确地抱怨无限类型。基本上无类型lambda演算中的所有都是一个函数 - 这实际上意味着所有函数都具有无限 arity。但是Haskell只允许有限度的函数。 (因为,真的,为什么你想要无限的灵魂?)

嗯,事实证明我们可以很容易地采取这种限制:

data Term = T (Term -> Term)

T f ! x = f x

i = T $ \ x -> x
k = T $ \ x -> T $ \ y -> x
s = T $ \ f -> T $ \ g -> T $ \ x -> (f ! x) ! (g ! x)

这非常有效,并且允许构造和执行任意lambda表达式。例如,我们可以轻松地构建一个函数来将Int转换为教会数字:

zero = k ! i
succ = s ! (s ! (k ! s) ! k)

encode 0 = zero
encode n = succ ! (encode $ n-1)

再一次,这很有效。

现在写一个解码函数。

...是的,祝你好运!麻烦的是,我们可以创建任意lambda术语,但我们无法以任何方式检查它们。所以我们需要添加一些方法来做到这一点。

到目前为止,我提出的最好的想法是:

data Term x = F (Term x -> Term x) | R (Term x -> x)

F f ! x =            f x
R f ! x = R $ \ _ -> f x

out :: Term x -> x
out (R f) = f (error "mu")
out (F _) =   (error "nu")

i = F $ \ x -> x
k = F $ \ x -> F $ \ y -> x
s = F $ \ f -> F $ \ g -> F $ \ x -> (f ! x) ! (g ! x)

我现在可以做类似

的事情了
decode :: Term Int -> Int
decode ti = out $ ti ! R (\ tx -> 1 + out tx) ! R (\ tx -> 0)

这对Church Bools和Church数字非常有用。

当我开始尝试做任何高阶事时,事情开始变得非常糟糕。扔掉所有类型信息以实现无类型 lambda演算后,我现在正在努力说服类型检查器让我做我想做的事。

这有效:

something = F $ \ x -> F $ \ n -> F $ \ s -> s ! x
nothing   =            F $ \ n -> F $ \ s -> n

encode :: Maybe x -> Term x
encode (Nothing) = nothing
encode (Just  x) = something ! x

这不是:

decode :: Term x -> Maybe (Term x)
decode tmx = out $ tmx ! R (\ tx -> Nothing) ! R (\ tx -> Just tx)

我已经尝试了十几个微小的变化;没有一种类型检查。并不是我不明白为什么会失败,而是我无法弄清楚它是否能够成功。 (特别是,R Just显然是病态的。)

这几乎就像我想要一个函数forall x y. Term x -> Term y。因为,对于无类型术语,这应该总是可行的。这是唯一涉及R但无法工作的条款。但我无法弄清楚如何在Haskell类型系统中表达这一点。

(例如,尝试将F的类型更改为forall x. Term x -> Term x。现在k的定义是错误的,因为内部F $ \ y -> x实际上无法返回< em>任何类型,但只有x的[现在已修复]类型。)

任何比我聪明的人都有更好的主意吗?

2 个答案:

答案 0 :(得分:4)

好的,我找到了 解决方案:

上面的代码有Term x,由R的结果类型参数化。而不是这样做(并且吓坏了类型检查器),构造一些类型Value,它可以表示您想要返回的每个结果类型。现在我们有了

data Term = F (Term -> Term) | R (Term -> Value)

将所有可能的结果类型合并为一个不透明的Value类型后,我们就可以开展工作了。

具体而言,我选择的类型是

data Value = V Int [Term]

因此Value是表示ADT值构造函数的Int,后跟该构造函数的每个字段一个Term。有了这个定义,我们终于可以做到了

decode :: Term -> Maybe Term
decode tmx =
  case tmx ! R (\ _ -> V 0 []) ! R (\ tx -> V 1 [tx]) of
    V 0 []   -> Nothing
    V 1 [tx] -> Just tx
    _        -> error "not a Maybe"

同样,您可以对列表进行编码和解码,如下所示:

null =                        F $ \ n -> F $ \ c -> n
cons = F $ \ x -> n $ \ xs -> F $ \ n -> F $ \ c -> c ! x ! xs

encode :: [Term] -> Term
encode (  []) = null
encode (x:xs) = cons ! x ! encode xs

decode :: Term -> [Term]
decode txs =
  case out $ txs ! R (\ txs -> V 0 []) ! F (\ tx -> R $ \ txs -> V 1 [tx, txs]) of
    V 0 []        -> []
    V 1 [tx, txs] -> tx : decode txs
    _             -> error "not a list"

当然,您必须猜测您需要应用哪些解码功能。但那是你的无类型lambda演算!

答案 1 :(得分:2)

这不是答案,但评论过于严格。

R Just是错误类型的,因为它的类型是递归的,但我们总是可以在数据类型中包装这种类型级别的递归:

data Fix2 g f = Fix2 { run :: g (f (Fix2 g f)) }

Fix2可以用Fix和类型构造函数的组合来表示,但我不想让事情复杂化。

然后我们可以将decode定义为

decode :: Term (Fix2 Maybe Term) -> Maybe (Term (Fix2 Maybe Term))
decode tmx = run $ out $ tmx ! R (Fix2 . const Nothing) ! R (Fix2 . Just)

一些测试:

isSomething :: Term (Fix2 Maybe Term) -> Bool
isSomething = isJust . decode

i = F id

main = do
    print $ isSomething (something ! i) -- True
    print $ isSomething  nothing        -- False

但显然Term (Fix2 Maybe Term)远离Term a