我对Haskell来说几乎是新手,所以如果我缺少关键概念,请指出。
让我们说我们有这两个功能:
fact n
| n == 0 = 1
| n > 0 = n * (fact (n - 1))
fact
的多态类型为 (Eq t, Num t) => t -> t
因为n
用于if条件且n必须是有效类型才能执行{{1}检查。因此,==
必须是t
,而Number
可以是类约束t
中的任何类型
Eq t
那为什么fib n
| n == 1 = 1
| n == 2 = 1
| n > 2 = fib (n - 1) + fib (n - 2)
的多态类型是 fib
?
我不明白,请帮助。
答案 0 :(得分:7)
Haskell总是旨在派生大多数泛型类型签名。
现在fact
,我们知道输出的类型应该与输入的类型相同:
fact n | n == 0 = 1
| n > 0 = n * (fact (n - 1))
这是由于最后一行。我们使用n * (fact (n-1))
。所以我们使用乘法(*) :: a -> a -> a
。因此乘法采用相同类型的两个成员并返回该类型的成员。由于我们乘以n
并输入n
,因此输出与输入的类型相同。由于我们使用n == 0
,因此我们知道(==) :: Eq a => a -> a -> Bool
,这意味着该输入类型应该包含Eq a =>
,还有0 :: Num a => a
。因此,结果类型为fact :: (Num a, 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
,我们还返回两个输出:{ {1}},再次fib (n-1) + fib (n-2)
。
答案 1 :(得分:5)
不同之处在于,在fact
中,您直接在算术表达式中使用该参数,该算术表达式构成了最终结果:
fact n | ... = n * ...
IOW,如果你写出扩展的算术表达式,其中会出现n
:
fact 3 ≡ n * (n-1) * (n-2) * 1
这修复了参数必须与结果具有相同类型的原因,因为
(*) :: Num n => n -> n -> n
在fib
中不是这样:这里的实际结果只包含文字和 sub - 结果。 IOW,扩展的表达式看起来像
fib 3 ≡ (1 + 1) + 1
此处没有n
,因此参数和结果之间不需要统一。
当然,在这两种情况下,你也使用n
来决定这个算术表达式的外观,但为此你刚刚使用了与文字相等的比较,其类型没有与最终结果相关联。
请注意,您还可以为fib
提供类型保留签名:(Eq a, Num a, Num t) => a -> t
严格地比(Eq t, Num t) => t -> t
更通用。相反,您可以通过使用转换函数将fact
设置为不需要输入和输出的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
,并且只在实际需要的位置添加转换操作。或者真的,我建议根本不使用npm install --save react-bootstrap
- 这是一个非常昂贵的功能,在实践中几乎没有用过;大多数天真地以阶乘结束的应用程序实际上只需要binomial coefficients之类的东西,而且这些应用程序可以在没有因子的情况下更有效地实现。