Haskell 为什么这两个函数有不同的类型?
我有两个功能Haskell 为什么这两个函数有不同的类型?,haskell,types,Haskell,Types,我有两个功能 (\f b -> (\a -> a) f b b) 及 我试图手动找到这些函数的类型,得到了 (t1 -> t1 -> t2) -> t1 -> t2 及 但是当我使用GHCI使用:t获取类型时,我得到了以下结果 (\f b -> (\a -> a) f b b) :: (t1 -> t1 -> t2) -> t1 -> t2 (\f b -> (\a -> 0) f b b) :: Num (
(\f b -> (\a -> a) f b b)
及
我试图手动找到这些函数的类型,得到了
(t1 -> t1 -> t2) -> t1 -> t2
及
但是当我使用GHCI使用:t获取类型时,我得到了以下结果
(\f b -> (\a -> a) f b b) :: (t1 -> t1 -> t2) -> t1 -> t2
(\f b -> (\a -> 0) f b b) :: Num (t1 -> t1 -> t2) => p -> t1 -> t2
我不明白如何将\a->a
更改为\a->0
将第一个参数从(t1->t1->t2)
更改为p
派生(\f b->(\a->a)f b)的类型
让我们试着推导表达式的类型:
(\f b -> (\a -> a) f b b)
或者更详细:
(\f -> (\b -> (((\a -> a) f) b) b))
我们在这里看到,这是一个包含两个参数的函数(从技术上讲,一个函数总是包含一个参数,然后该函数的结果可以包含另一个参数,但让我们假设,如果我们谈论“两个参数”,我们指的是这样的构造)
因此,参数是f
和b
,最初我们对这些参数了解不多,因此我们为它们指定了一个类型,表达式为:
f :: g
b :: h
(\f b -> (\a -> a) f b b) :: g -> (h -> i)
(\f b -> (\a -> a) f b b) :: (h -> (h -> i)) -> (h -> i)
因此,我们创建了三种类型g
、h
和i
(我在这里使用了a
、b
和c
以外的其他标识符,因为这可能会导致变量混淆)
但是我们还没有完成,因为表达式本身可以对类型的行为引入更多的约束。例如,我们看到一个lambda表达式:\a->a
,它显然具有as类型:
\a -> a :: j -> j
接下来我们将看到一个函数应用程序,其中\a->a
作为函数,f
作为参数,这意味着g~j
(g
和j
是相同的类型),(\a->a)f
的类型是(\a->a)f::g
但是我们还没有完成,因为(\a->a)f
的结果现在在b
的函数应用程序中充当函数,这意味着g
实际上是一个函数,具有输入类型h
,以及一些(当前未知的输出类型),因此:
因此(\a->a)fb
的类型是k
,但我们仍然没有完成,因为我们执行另一个函数应用程序,将(\a->a)fb
作为函数(typek
),将b
作为参数,这意味着k
实际上是一个函数,将h
作为参数类型,结果就是表达式的类型,所以i
。这意味着我们有:
g ~ j
g ~ (h -> k)
k ~ (h -> i)
换句话说,表达式的类型是:
f :: g
b :: h
(\f b -> (\a -> a) f b b) :: g -> (h -> i)
(\f b -> (\a -> a) f b b) :: (h -> (h -> i)) -> (h -> i)
或者更详细一些:
(\f b -> (\a -> a) f b b) :: (h -> h -> i) -> h -> i
(\f b -> (\a -> 0) f b b) :: Num (h -> h -> i) => g -> h -> i
为(\f b->(\a->0)f b b)派生类型
推导的第一步大致相同,我们首先介绍一些类型变量:
f :: g
b :: h
(\f b -> (\a -> 0) f b b) :: g -> (h -> i)
现在我们开始推断。我们首先推断(\a->0)
的类型。这是一个函数,类型为Num l=>j->l
,因为0
是Num
ber,但它可以是任何Num
类型,与参数a
的类型无关
接下来我们看到有一个函数调用,(\a->0)
作为函数,f
作为参数,因此我们得出结论,g~j
。此函数调用的结果类型为(\a->0)f::Num l=>l
现在我们看到另一个函数调用,函数为(\a->0)f
,参数为b
。因此我们得出结论,l
是一个函数(因此l~(h->k)
)
最后一个函数调用是使用(\a->0)f b::k
作为函数,并再次使用b
作为参数。这意味着k
是一个函数k~h->i
。因此,我们得到以下类型和等式:
f :: g
b :: h
(\a -> 0) :: Num l => j -> l
(\f b -> (\a -> 0) f b b) :: g -> (h -> i)
g ~ j
l ~ (h -> k)
k ~ (h -> i)
因此,表达式的类型为:
(\f b -> (\a -> 0) f b b) :: g -> (h -> i)
或者更具体地说:
(\f b -> (\a -> 0) f b b) :: Num (h -> (h -> i)) => g -> (h -> i)
或者更详细一些:
(\f b -> (\a -> a) f b b) :: (h -> h -> i) -> h -> i
(\f b -> (\a -> 0) f b b) :: Num (h -> h -> i) => g -> h -> i
因此,由于我们使用内部lambda表达式(\a->0)
,因此f
的类型或值不再相关(\a->0)f
将始终返回0
,这应该是一个函数,可以考虑ab
至少从理论的角度来看,对于一个Num
(只要它支持应该由Num
类型实现的函数)的函数没有什么“奇怪”的地方。例如,我们可以实现一个函数实例Num(a->b->Int)
,从而将0
视为一个常量函数,始终映射到0
,并将(+)
视为构造一个新函数的方法,将两个函数相加。因为您的函数\a->0
返回一个数字,而对于\a->a
,它返回参数,因此输出与输入类型相同。您希望发生什么?您正在指定一个返回类型,即一个数字,因此类型当然会改变!什么特别让你困惑?当然,但是为什么第一个参数现在是p
而不是(t1->t1->t2)
@joelfischer:因为第一个参数是f
(在\f b->…
),而f
因此是一个自由参数,因为我们使用它作为(\a->0)
的参数,这意味着无论我们写什么,它都是“被忽略的”。fbb
不是代码中的子表达式。函数应用程序是左关联的,因此它解析为((\a->0)f)b)b
。