Function 在Haskell中,两个相似的函数如何具有不同的多态类型?

Function 在Haskell中,两个相似的函数如何具有不同的多态类型?,function,haskell,types,polymorphism,fibonacci,Function,Haskell,Types,Polymorphism,Fibonacci,我对Haskell很陌生,所以如果我缺少关键概念,请指出 假设我们有两个功能: 事实 |n==0=1 |n>0=n*(事实(n-1)) fact的多态类型是(Eq t,Num t)=>t->t,因为在if条件中使用了n,n必须是有效类型才能执行=检查。因此t必须是Number并且t可以是类约束内的任何类型Eq t fib n |n==1=1 |n==2=1 |n>2=fib(n-1)+fib(n-2) 那么为什么fib的多态类型是(Eq a,Num a,Num t)=>a->t 我不明白,请

我对Haskell很陌生,所以如果我缺少关键概念,请指出

假设我们有两个功能:

事实 |n==0=1 |n>0=n*(事实(n-1))
fact
的多态类型是
(Eq t,Num t)=>t->t
,因为在if条件中使用了
n
,n必须是有效类型才能执行
=
检查。因此
t
必须是
Number
并且
t
可以是类约束内的任何类型
Eq t

fib n
|n==1=1
|n==2=1
|n>2=fib(n-1)+fib(n-2)
那么为什么
fib
的多态类型是
(Eq a,Num a,Num t)=>a->t


我不明白,请帮忙。

区别在于在
事实中,您直接在算术表达式中使用参数,该表达式构成最终结果:

fact n | ...  = n * ...
这样,如果写出展开的算术表达式,
n
将出现在其中:

fact 3  ≡  n * (n-1) * (n-2) * 1
这修复了参数必须与结果具有相同类型的问题,因为

(*) :: Num n => n -> n -> n
fib
中并非如此:此处实际结果仅由文字和子结果组成。瞧,扩展表达式看起来像

fib 3  ≡  (1 + 1) + 1
这里没有
n
,所以参数和结果之间不需要统一

当然,在这两种情况下,您还使用了
n
来确定这个算术表达式的外观,但为此,您只使用了与文本的相等比较,文本的类型与最终结果无关

请注意,您还可以为
fib
提供一个类型保留签名:
(Eq a,Num a,Num t)=>a->t
严格来说比
(Eq t,Num t)=>t->t
更一般。相反,通过使用转换函数,您可以创建一个不要求输入和输出为同一类型的
事实:

fact' :: (Eq a, Integral a, Num t) => a -> t
fact' = fromIntegral . fact
但这没有多大意义,因为
Integer
几乎是唯一可以在
fact
中可靠使用的类型,但要在上述版本中实现这一点,您需要从
Integer
开始。因此,如果有任何问题,您应该执行以下操作:

fact'' :: (Eq t, Integral a, Num t) => a -> t
fact'' = fact . fromIntegral
然后还可以将其用作
Int->Integer
,这有点合理


我建议只保留签名
(Eq t,Num t)=>t->t
,并且只在实际需要的地方添加转换操作。或者说真的,我建议根本不要使用
fact
——这是一个非常昂贵的函数,在实践中几乎没有什么用处;大多数以阶乘结束的应用程序实际上只需要这样的东西,而这些应用程序可以在没有阶乘的情况下更有效地实现。

Haskell始终致力于派生最通用的类型签名

现在对于
事实
,我们知道输出的类型应该与输入的类型相同:

fact n | n == 0 = 1
       | n > 0  = n * (fact (n - 1))
这是由于最后一行。我们使用
n*(事实(n-1))
。所以我们使用乘法
(*)::a->a->a
。因此,乘法取相同类型的两个成员并返回该类型的一个成员。由于我们与
n
相乘,并且输入
n
,因此输出与输入的类型相同。由于我们使用
n==0
,我们知道
(==)::Eq a=>a->a->Bool
,这意味着该输入类型应该有
Eq a=>
,而且
0::Num a=>a
。因此,结果类型是
fact::(numa,Eq a)=>a->a

现在对于
fib
,我们看到:

fib n | n == 1 = 1
      | n == 2 = 1
      | n > 2  = fib (n - 1) + fib (n - 2)

现在我们知道,对于
n
,类型约束再次是
Eq a,Num a
,因为我们使用
n==1
,和
(==)::Eq a=>a->a->Bool
1::Num a=>a
。但是,输入
n
从未直接用于输出
。实际上,最后一行有
fib(n-1)+fib(n-2)
,但这里我们使用
n-1
n-2
作为新呼叫的输入。这意味着我们可以安全地确定输入类型和输出类型是独立工作的。输出类型仍然有一个类型约束:
Num t
:这是因为对于前两种情况,我们返回
1
,而
1::Num t=>t
,并且我们还返回两个输出的加法:
fib(n-1)+fib(n-2)
,所以同样
(+)::Num t=>t->t->t

因为您没有在输出中使用输入参数。很抱歉,我没有完全理解您所说的@威廉·瓦农森