在Haskell中实现多态λ演算/系统F

时间:2015-11-11 18:07:10

标签: haskell functional-programming polymorphism lambda-calculus church-encoding

我想在Haskell中实现多态lambda演算中的Church encoding of the pair

Peter Selinger's notes on lambda calculus的第77页第8.3.3节中,他给出了两种类型的笛卡尔积的构造

A×B =∀α。(A→B→α)→α
⟨M,N⟩=Λα.λf A→B→α .fMN

对于另一个来源,在第54页,Dider Rémy's notes on lambda calculus的第4.2.3节,他将多态λ-演算/系统F中的对的教会编码定义为

Λα₁.Λα₂.λx₁:α₁.λx₂:α₂.Λβ.λy:α₁→α₂→β。 y x 1 x 2

我认为雷米和塞林格一样,更加冗长地说了同样的话。

无论如何,根据维基百科,Haskell的类型系统基于System F,所以我希望有可能直接在Haskell中实现这个Church编码。我有:

pair :: a->b->(a->b->c)->c
pair x y f = f x y

但我不确定如何进行预测。

Λα.Λβ.λpα×β.pα(λxα.λyβ .X)

我是否使用Haskell forall作为大写lambda类型量词?

这与my previous question基本相同,但在Haskell而不是Swift中。我认为额外的背景和场地的变化可能会让它变得更加明智。

2 个答案:

答案 0 :(得分:8)

首先,你确实是正确的,Selinger和Rémy说同样的话;不同之处在于Rémy定义了对构造函数, - , - ⟩,它将M和N(他的x 1和x 2)及其类型(α1和α2)作为参数;他的定义的其余部分只是⟨M,N⟩与β和y的定义,其中Selinger有α和f。

好的,在照顾的情况下,让我们开始移动towrads投影。首先要注意的是∀,Λ,→和λ之间的关系,以及它们在Haskell中的等价物。回想一下∀及其居民Λ在类型上运行,其中→其居民λ在上运行。在Haskell-land中,大多数这些对应都很简单,我们得到下表

          System F                             Haskell
Terms (e)     :  Types (t)        Terms (e)       ::  Types (t)
────────────────────────────────────────────────────────────────
λx:t₁.(e:t₂)  :  t₁ → t₂          \x::t₁.(e::t₂)  :: t₁ -> t₂
Λα.(e:t)      :  ∀α.t             (e::t)          :: forall α. t

术语级别条目很简单:→变为->,λ变为\。但是∀和Λ呢?

默认情况下,在Haskell中,所有的are都是隐式的。每当我们引用一个类型变量(一个类型中的小写标识符)时,它就会被隐含地普遍量化。所以像

这样的类型签名
id :: a -> a

对应

id:∀α.α→α

系统F中的

我们可以启用语言扩展ExplicitForAll并获得明确写入的能力:

{-# LANGUAGE ExplicitForAll #-}
id :: forall a. a -> a

但是,默认情况下,Haskell只允许我们将这些量词放在定义的开头;我们希望System F风格能够将forall放在我们的类型中的任何位置。为此,我们启用RankNTypes。实际上,从现在开始,所有Haskell代码都将使用

{-# LANGUAGE RankNTypes, TypeOperators #-}

(另一个扩展名允许类型名称为运算符。)

现在我们知道了这一切,我们可以尝试写下×的定义。我将其Haskell版本**称为保持不同(尽管我们可以使用×)。塞林格的定义是

A×B =∀α。(A→B→α)→α

所以Haskell是

type a ** b = forall α. (a -> b -> α) -> α

正如你所说,创建功能是

pair :: a -> b -> a ** b
pair x y f = f x y

但是我们的Λs发生了什么?他们在SystemM,N⟩的系统F定义中存在,但pair没有任何!

所以这是我们表格中的最后一个单元格:在Haskell中,所有Λ都是隐含的,甚至没有一个扩展来使它们显式化.¹他们会出现的任何地方,我们只是忽略它们和类型推断自动填充它们。因此,要直接回答您的一个明确问题,您可以使用Haskell forall来表示系统F∀,并使用 nothing 来表示系统F类型lambdaΛ。

因此,您将第一个投影的定义指定为(重新格式化)

proj 1 =Λα.Λβ.λp:α×β.pα(λx:α.λy:β.x)

我们通过忽略所有Λs及其应用程序(和eliding typeannotations²)将其转换为Haskell,然后我们得到

proj₁ = \p. p (\x y -> x)

proj₁ p = p (\x _ -> x)

我们的System F版本的类型为

proj 1:∀α.∀β。 α×β→α

或,扩展

proj 1:∀α.∀β。 (∀γ.α→β→γ)→α

实际上,我们的Haskell版本具有类型

proj₁ :: α ** β -> α

再次扩展到

proj₁ :: (forall γ. α -> β -> γ) -> α

或者,要明确αβ的绑定,

proj₁ :: forall α β. (forall γ. α -> β -> γ) -> α

为了完整起见,我们也有

proj 2:∀α.∀β。 α×β→β
proj 2 =Λα.Λβ.λp:α×β.pβ(λx:α.λy:β.y)

成为

proj₂ :: α ** β -> β
proj₂ p = p (\_ y -> y)

在这一点上可能并不令人惊讶: - )

¹相关地,所有Λ都可以在编译时擦除 - 编译后的Haskell代码中不存在类型信息!

²我们忽略Λs这一事实意味着类型变量在术语中不受约束。以下是错误:

id :: a -> a
id x = x :: a

因为它被视为我们写的

id :: forall a. a -> a
id x = x :: forall b. b

当然不起作用。为了解决这个问题,我们可以启用语言扩展ScopedTypeVariables;然后,明确forall中绑定的任何类型变量都在该术语的范围内。所以第一个例子仍然存在,但是

id :: forall a. a -> a
id x = x :: a

工作正常。

答案 1 :(得分:1)

你写了

Λα.Λβ.λp:α×β.p α (λx:α.λy:β.x)

只需删除应用程序和抽象中的所有类型参数:

λp:α×β.p (λx:α.λy:β.x)

在Haskell中,没有类型注释:

\p -> p (\x y -> x)