我想在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中。我认为额外的背景和场地的变化可能会让它变得更加明智。
答案 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)