我刚刚开始玩一点Haskell......我想写一个相同类型的身份函数。显然,不等同于它。那会是这样的,
myfunction :: a -> a
我想不出一个参数和返回类型相同并且几乎可以是任何东西的例子(这排除了使用 Haskell 的 Typeclasses 的可能性)。
我刚刚开始玩一点Haskell......我想写一个相同类型的身份函数。显然,不等同于它。那会是这样的,
myfunction :: a -> a
我想不出一个参数和返回类型相同并且几乎可以是任何东西的例子(这排除了使用 Haskell 的 Typeclasses 的可能性)。
undefined
如果不使用另一位提到的评论者,这是不可能的。让我们通过反例来证明它。假设有这样一个函数:
f :: a -> a
当您说这与 不同时id
,这意味着您无法定义:
f x = x
但是,考虑a
type的情况()
:
f () = ...
唯一可能f
返回的结果是()
,但这将是与 相同的实现id
,因此是矛盾的。
更复杂和严格的答案是表明类型a -> a
必须同构于()
. 当我们说两种类型a
并且b
是同构的时,这意味着我们可以定义两个函数:
fw :: a -> b
bw :: b -> a
...这样:
fw . bw = id
bw . fw = id
当第一种类型是a -> a
,第二种类型是时,我们可以很容易地做到这一点()
:
fw :: (forall a . a -> a) -> ()
fw f = f ()
bw :: () -> (forall a . a -> a)
bw () x = x
那么我们可以证明:
fw . bw
= \() -> fw (bw ())
= \() -> fw (\x -> x)
= \() -> (\x -> x) ()
= \() -> ()
= id
bw . fw
= \f -> bw (fw f)
-- For this to type-check, the type of (fw f) must be ()
-- Therefore, f must be `id`
= \f -> id
= \f -> f
= id
当您证明两种类型是同构的时,您知道的一件事是,如果一种类型包含有限数量的元素,那么另一种也必须如此。由于该类型()
只包含一个值:
data () = ()
这意味着该类型(forall a . a -> a)
也必须只包含一个值,而这恰好是id
.
编辑:有些人评论说同构的证明不够严格,所以我将调用米田引理,当翻译成 Haskell 时,它对任何函子说f
:
(forall b . (a -> b) -> f b) ~ f a
Where~
意味着它(forall b . (a -> b) -> f b)
与f a
. 如果您选择Identity
函子,这将简化为:
(forall b . (a -> b) -> b) ~ a
...如果您选择a = ()
,这将进一步简化为:
(forall b . (() -> b) -> b) ~ ()
你可以很容易地证明它() -> b
同构于b
:
fw :: (() -> b) -> b
fw f = f ()
bw :: b -> (() -> b)
bw b = \() -> b
fw . bw
= \b -> fw (bw b)
= \b -> fw (\() -> b)
= \b -> (\() -> b) ()
= \b -> b
= id
bw . fw
= \f -> bw (fw f)
= \f -> bw (f ())
= \f -> \() -> f ()
= \f -> f
= id
所以我们可以使用它来最终将米田同构特化为:
(forall b . b -> b) ~ ()
这表示任何类型forall b . b -> b
的函数都与()
. 米田引理提供了我的证明缺失的严谨性。
让我制定一个详细说明 dbaupp 评论的答案。任何类型的函数a -> a
也会产生一个类型的函数() -> ()
,所以我先看看这个子问题。
Haskell 类型和函数的通常语义将类型表示为指向链完整的偏序,并将函数表示为连续函数。类型()
由两个元素集{⊥,()} 表示,顺序为⊥⊏()。在普通集合论中,从这个集合到自身有 2^2=4 个函数,但其中只有三个是连续的:
所以在我们的语义模型中, type 有三种不同的功能() -> ()
。但是其中哪些可以在 Haskell 中实现呢?他们都是!
f1 _ = undefined
(或f1 x = f1 x
)f2 x = x
(或f2 = id
)f3 _ = ()
(或f3 = const ()
)查看这些定义,您可以看到f1
并且f2
也可以用于定义 type 的函数a -> a
。由于他们已经在做不同的事情()
,所以他们是不同的。所以我们至少有两个不同的 type 函数a -> a
。
在上面的语义模型中,还有很多 type 的函数a -> a
,但是这些函数在 Haskell 中是无法表达的(这与参数化和 Wadler 的Free 定理有关)。正确证明f1
并且f2
是唯一这样的函数似乎并不容易,因为它取决于 Haskell 语言不允许的内容(例如,参数类型没有模式匹配)。
除非您愿意使用 undefined 或 bottom(非终止表达式),否则实际上没有其他函数可以满足该类型。
这是 Haskell 类型系统的一大优势。可以强烈限制可以通过编译器传递给显然正确的函数。举一个极端的例子,见djinn——它接受一个类型,并生成与该类型匹配的可能函数。即使是真实的、复杂的例子,这个列表通常也很短。
这里的关键是要了解我们对 . 什么都不知道a
,尤其是我们无法生成一个新的或将其转换为不同的东西。因此,我们别无选择返回它(或底部值)。一旦我们有了更多关于a
(例如上下文绑定)的信息,我们就可以用它做更多有趣的事情:
f :: Monoid a => a -> a
f _ = mempty
或者
f :: Monoid a => a -> a
f x = x `mappend` x `mappend` x
或者,如果您有类似 in 的选择f :: (a, a) -> a
,您有两种可能的实现(再次忽略底部值),但f :: (a, b) -> a
您又回到了一个实现,这与 for 相同fst
:虽然f
使用一对相同的类型调用是有效的,例如f ("x", "y")
,您可以确定它的f
行为类似于fst
,因为在实现中f
您无法测试两种参数类型是否相同。同样,只有一个非底层版本的f :: (a -> b) -> a -> b
.
多态性限制了自由度,因为你对你的论点一无所知,在某些情况下,它归结为一个非底层版本。
正如其他人所提到的,不存在这样的其他总功能。(如果我们不将自己限制在总功能上,那么我们可以通过 . 来居住任何类型undefined
。)
我将尝试基于 λ 演算给出一个理论解释:
为简单起见,让我们将自己限制在 λ 项(我们可以将任何 Haskell 表达式翻译成它)。对于 λ 项,如果不是应用程序(也可以为零),M
我们称它为头。请注意,如果是正常形式,则不能是 λ-抽象,除非.A
M ≡ A N1 ... Nk
A
k
M
A
k = 0
所以让M :: a -> a
成为范式的 λ-项。由于我们在上下文中没有变量,M
因此不能是变量,也不能是应用程序。如果是,它的头部就必须是一个变量。所以M
一定是λ-抽象,一定是M ≡ λ(x:a).N
。
现在N
必须是类型的a
,正式的{x:a}⊢N:a
。如果N
是 λ-抽象,则其类型为σ -> τ
,这是不可能的。如果N
是一个函数应用程序,那么它的 head 必须是一个变量,而我们在上下文中唯一的一个是x
. 但是因为x:a
,我们不能应用于x
任何东西,x P
所以不能对任何 P 进行录音。所以唯一的可能性是N ≡ x
。所以,M
一定是λ(x:a).x
。
(如果可能,请更正我的英语。特别是,我不确定如何使用虚拟语气权利)。