我只想指出,数据类型和模式匹配(初步近似)只是有用但冗余的语言特性,可以纯粹使用 lambda 演算来实现。如果您了解如何在 lambda 演算中实现它们,您就可以理解为什么它们不需要Eq
实现模式匹配。
在 lambda 演算中实现数据类型被称为“教堂编码”(在Alonzo Church之后,他演示了如何做到这一点)。Church-encoded 函数也被称为“Continuation-passing style”。
它被称为“连续传递样式”,因为您提供了一个处理该值的函数,而不是提供值。因此,例如,Int
我可以不给用户一个 ,而是给他们一个以下类型的值:
type IndirectInt = forall x . (Int -> x) -> x
上述类型与Int
. “同构”只是一种奇特的说法,我们可以将 anyIndirectInt
转换为Int
:
fw :: IndirectInt -> Int
fw indirect = indirect id
...我们可以将 anyInt
转换为IndirectInt
:
bw :: Int -> IndirectInt
bw int = \f -> f int
...这样:
fw . bw = id -- Exercise: Prove this
bw . fw = id -- Exercise: Prove this
使用延续传递风格,我们可以将任何数据类型转换为 lambda 演算项。让我们从一个简单的类型开始,例如:
data Either a b = Left a | Right b
在延续传递风格中,这将变为:
type IndirectEither a b = forall x . (Either a b -> x) -> x
但是 Alonzo Church 很聪明,他注意到对于任何具有多个构造函数的类型,我们可以只为每个构造函数提供一个单独的函数。因此,在上述类型的情况下(Either a b -> x)
,我们可以提供两个单独的函数,一个用于 ,一个用于 ,而不是提供a
type的函数,b
这同样好:
type IndirectEither a b = forall x . (a -> x) -> (b -> x) -> x
-- Exercise: Prove that this definition is isomorphic to the previous one
那么Bool
构造函数没有参数的类型呢?好吧,Bool
与 (Exercise: Prove this) 同构Either () ()
,所以我们可以将其编码为:
type IndirectBool = forall x . (() -> x) -> (() -> x) -> x
并且() -> x
与 (Exercise: Prove this) 同构x
,因此我们可以进一步将其重写为:
type IndirectBool = forall x . x -> x -> x
只有两个函数可以具有上述类型:
true :: a -> a -> a
true a _ = a
false :: a -> a -> a
false _ a = a
由于同构,我们可以保证所有 Church 编码将具有与原始数据类型的可能值一样多的实现。因此,恰好有两个函数存在于其中并非巧合,IndirectBool
就像 恰好有两个构造函数一样Bool
。
但是我们如何在我们的 ? 上进行模式匹配IndirectBool
?例如,对于普通的Bool
,我们可以只写:
expression1 :: a
expression2 :: a
case someBool of
True -> expression1
False -> expression2
好吧,我们的IndirectBool
它已经带有解构自身的工具。我们只想写:
myIndirectBool expression1 expression2
请注意,如果myIndirectBool
是true
,它将选择第一个表达式,如果是false
,它将选择第二个表达式,就像我们以某种方式对其值进行了模式匹配一样。
让我们尝试对IndirectEither
. 使用普通的Either
,我们会写:
f :: a -> c
g :: b -> c
case someEither of
Left a -> f a
Right b -> g b
有了IndirectEither
,我们只需要写:
someIndirectEither f g
简而言之,当我们以延续传递风格编写类型时,延续就像 case 构造的 case 语句,所以我们所做的就是将每个不同的 case 语句作为参数传递给函数。
这就是您不需要Eq
对类型进行模式匹配的原因。在 lambda 演算中,类型决定它“等于”什么,只需定义它从提供给它的参数中选择哪个参数。
因此,如果我是,我通过始终选择我的第一个参数true
来证明我是“平等的” 。true
如果我是 a ,我通过总是选择我的第二个参数false
来证明我是“平等的” 。false
简而言之,构造函数“相等”归结为“位置相等”,它总是在 lambda 演算中定义,如果我们可以将一个参数区分为“第一个”,另一个作为“第二个”,这就是我们所需要的“比较”构造函数的能力。