简体   繁体   English

与无类型lambda演算的冒险

[英]Adventures with the untyped lambda calculus

We occasionally have people ask about implementing the untyped lambda calculus in Haskell. 我们偶尔会有人询问在Haskell中实现无类型的lambda演算。 [Naturally, I now cannot find any of these questions, but I'm sure I've seen them!] Just for giggles, I thought I'd spend some time playing with this. [当然,我现在找不到任何这些问题,但我确定我已经看过了!]只是为了咯咯笑,我以为我会花一些时间玩这个。

It's trivial enough to do something like 做一些像这样的事情是微不足道的

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

This works perfectly. 这非常有效。 However, as soon as you try to do something like 但是,只要你尝试做类似的事情

s i i

the type-checker rightly complains about an infinite type. 类型检查员正确地抱怨无限类型。 Basically everything in the untyped lambda calculus is a function — which essentially means all functions have infinite arity. 基本上,无类型lambda演算中的所有东西都是一个函数 - 这实际上意味着所有函数都具有无限的特征。 But Haskell only allows functions of finite arity. 但是Haskell只允许有限度的函数。 (Because, really, why would you want infinite arity?) (因为,真的,为什么你想要无限的灵魂?)

Well, it turns out we can easily side-step that limitation: 好吧,事实证明我们可以很容易地采取这种限制:

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)

This works perfectly, and allows arbitrary lambda expressions to be constructed and executed. 这非常有效,并且允许构造和执行任意lambda表达式。 For example, we can easily build a function to turn an Int into a Church numeral: 例如,我们可以轻松地构建一个函数来将Int转换为Church数字:

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

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

Again, this works perfectly. 再次,这完美地工作。

Now write a decode function. 现在写一个解码函数。

…yeah, good luck with that! ...是的,祝你好运! The trouble is, we can create arbitrary lambda terms, but we can't inspect them in any way. 麻烦的是,我们可以创建任意的lambda术语,但我们无法以任何方式检查它们。 So we need to add some way to do that. 所以我们需要添加一些方法来做到这一点。


So far, the best idea I've come up with is this: 到目前为止,我提出的最好的想法是这样的:

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)

I can now do something like 我现在可以做点什么了

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

This works great for Church Bools and Church numerals. 这适用于Church Bools和Church数字。


Things start to go horribly wrong when I start trying to do anything high-order. 当我开始尝试做任何高阶事时,事情开始变得非常糟糕。 Having thrown away all the type information to implement the untyped lambda calculus, I'm now struggling to convince the type checker to let me do what I want to do. 抛弃所有类型信息来实现无类型的 lambda演算,我现在正在努力说服类型检查器让我做我想做的事情。

This works: 这有效:

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

This does not: 这不是:

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

I've tried a dozen slight variations on this; 我已经尝试了十几个微小的变化; none of them type-check. 没有一种类型检查。 It's not that I don't understand why it fails, but rather than I can't figure out any way for it to succeed. 并不是我不明白它为什么会失败,而是我无法弄清楚它取得成功的方法。 (Most particularly, R Just is clearly ill-typed.) (特别是, R Just显然是病态的。)

It's almost as if I want a function forall x y. Term x -> Term y 这几乎就像我想要一个函数一样forall x y. Term x -> Term y forall x y. Term x -> Term y . forall x y. Term x -> Term y Because, for pure untyped terms, this should always be possible. 因为,对于纯粹的无类型术语,这应该始终是可能的。 It's only terms involving R where that won't work. 这是唯一涉及R的术语,它不起作用。 But I can't figure out how to phrase that in the Haskell type system. 但我无法弄清楚如何在Haskell类型系统中表达这一点。

(For example, try changing the type of F to forall x. Term x -> Term x . Now the definition of k is ill-typed, since the inner F $ \\ y -> x cannot actually return any type, but only the [now fixed] type of x .) (例如,尝试将F的类型更改为forall x. Term x -> Term x 。现在k的定义是错误类型的,因为内部F $ \\ y -> x实际上不能返回任何类型,但只有[现已修复] x类型。)

Any anybody smarter than me have a better idea? 任何比我聪明的人都有更好的主意吗?

OK, I've found a solution: 好的,我找到了一个解决方案:

The code above has Term x , parameterised by the result type for R . 上面的代码有Term x ,由R的结果类型参数化。 Instead of doing that (and freaking out the type checker), construct some type Value that can represent every result type you will ever want to return. 而不是这样做(并吓坏了类型检查器),构造一些类型的Value ,可以表示您将要返回的每个结果类型。 Now we have 现在我们有

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

Having collapsed all possible result types into a single opaque Value type, we can do our work. 将所有可能的结果类型折叠为单个不透明的Value类型后,我们就可以完成工作了。

Concretely, the type I chose is 具体来说,我选择的类型是

data Value = V Int [Term]

So a Value is an Int representing an ADT value constructor, followed by one Term for each field of that constructor. 因此, Value是表示ADT值构造函数的Int ,后跟该构造函数的每个字段的一个Term With this definition, we can finally do 有了这个定义,我们终于可以做到了

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"

Similarly, you can encode and decode lists like so: 同样,您可以对列表进行编码和解码,如下所示:

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"

Of course, you have to guess which decode function(s) you need to apply. 当然,您必须猜测需要应用哪些解码功能。 But that's the untyped lambda calculus for you! 但那是你的无类型lambda演算!

This is not an answer, but commenting is too restrictive. 这不是答案,但评论过于严格。

R Just is ill-typed, because its type is recursive, but we can always wrap this type-level recursion in a data type: R Just是错误类型的,因为它的类型是递归的,但我们总是可以在数据类型中包装这种类型级别的递归:

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

Fix2 can be represented in terms of Fix and composition of type constructors, but I don't want to complicate things. Fix2可以用类型构造函数的Fix和组合来表示,但我不想让事情复杂化。

Then we can define decode as 然后我们可以将decode定义为

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

Some tests: 一些测试:

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

i = F id

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

But clearly Term (Fix2 Maybe Term) is far from Term a . 但显然, Term (Fix2 Maybe Term)远非Term a

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM