为什么“(1 + 1.0)”的类型是 "Fractional a => a" 而不是 "Num a => a"?
Why does "(1 + 1.0)" have the type "Fractional a => a" and not "Num a => a"?
1
具有类型 Num a => a
1.0
的类型为 Fractional a => a
为什么 1+1.0
的类型是 Fractional a => a
这对我来说似乎很奇怪,因为 1
不是小数。只有 1.0
是小数。那么1
是怎么变成小数并和1.0
组合成小数的呢?
因为只有 Num
有 + 运算符,所以如果 1.0
变成 Num
与 1
结合产生最终结果对我来说似乎更自然Num
(尽管这也很奇怪,因为我们会丢失从 1.0
到 1
的信息)。
每个分数都是 Num
,但并非每个 Num
都是 Fractional
。因此,如果我们有一个像 1
这样的 Num
,它可能是 Fractional
(因为有些 Num
是 Fractional
),也可能不是。但是1.0
只能是Fractional
,绝对不能是其他的Num
,比如Integer
.
因此,当编译器看到您将 1 添加到 Fractional
时,它意识到 1
在这种情况下也必须是 Fractional
- 否则您不会允许将其添加到 Fractional
.
这里有一个类似的例子,只涉及用户定义类型 类 而不是 Num
s。也许这会让你更清楚:
class Foo a where
foo :: a
class Foo a => Bar a where
bar :: a
combine :: a -> a -> a
通过上面的类型类我们现在有以下方法:
foo :: Foo a => a
bar :: Bar a => a
combine :: Bar a => a -> a -> a
所以现在让我们尝试像这样组合 foo
和 bar
:
combine foo bar
这大致相当于您尝试在示例中添加 1
(Num a => a
类型)和 1.0
(Fractional a => a
类型)。就像您的示例一样,它工作正常并且类型为 Bar a => a
.
- 数字
1
(技术上代表fromInteger
应用于Integer
值1
)属于class[=14]中的所有类型=].
- class
Fractional
中的所有类型也属于classNum
。
因此,
编号1
属于classFractional
中的所有类型。
类型class非常不像面向对象class,这一点怎么强调也不为过。
特别是,“如果 1.0
变成 Num
” 没有任何意义。 Num
是一种类型 class,而不是一种类型,因此任何东西都不能“变成 Num
”。事实上,在 Haskell 中,没有任何东西会变成其他东西——一切都有一个具体的类型,它是固定的。
现在你问,多态函数是如何工作的?嗯,它被称为 参数 多态是有原因的:看起来是“任意类型 a
” 实际上是 类型参数 。就像函数参数一样,从它们可以在事后更改其值的意义上说,它们不是变量,但从允许函数调用者为 a
– 只要它满足类型-class 约束。
所以在某种意义上,文字 1
是一个函数:它接受一个 类型的参数 a
和 returns 一个值1 :: a
。它要求的是 a
在 class Num
.
然后我们有 (+)
和 1.0
,它们都需要相同的 a
参数。 (+)
再次需要 Num
,没什么新鲜的;但是 1.0
需要 Fractional a
。所以总而言之,1 + 1.0
是一个函数,它接受类型参数 a
的“三个副本”,并且需要
Num a
Num a
Fractional a
– 这也需要 Num a
因为 Num
是 Fractional
. 的超级 class
如果我们真的必须把类型写成
,那就太尴尬了
(1 + 1.0) :: (Num a, Num a, Fractional a, Num a) => a
所以允许省略多余的约束,只留下Fractional a
,这意味着其余所有。我们不能做的是只保留 Num
约束之一,因为那并不意味着 Fractional
.
1
具有类型 Num a => a
1.0
的类型为 Fractional a => a
为什么 1+1.0
的类型是 Fractional a => a
这对我来说似乎很奇怪,因为 1
不是小数。只有 1.0
是小数。那么1
是怎么变成小数并和1.0
组合成小数的呢?
因为只有 Num
有 + 运算符,所以如果 1.0
变成 Num
与 1
结合产生最终结果对我来说似乎更自然Num
(尽管这也很奇怪,因为我们会丢失从 1.0
到 1
的信息)。
每个分数都是 Num
,但并非每个 Num
都是 Fractional
。因此,如果我们有一个像 1
这样的 Num
,它可能是 Fractional
(因为有些 Num
是 Fractional
),也可能不是。但是1.0
只能是Fractional
,绝对不能是其他的Num
,比如Integer
.
因此,当编译器看到您将 1 添加到 Fractional
时,它意识到 1
在这种情况下也必须是 Fractional
- 否则您不会允许将其添加到 Fractional
.
这里有一个类似的例子,只涉及用户定义类型 类 而不是 Num
s。也许这会让你更清楚:
class Foo a where
foo :: a
class Foo a => Bar a where
bar :: a
combine :: a -> a -> a
通过上面的类型类我们现在有以下方法:
foo :: Foo a => a
bar :: Bar a => a
combine :: Bar a => a -> a -> a
所以现在让我们尝试像这样组合 foo
和 bar
:
combine foo bar
这大致相当于您尝试在示例中添加 1
(Num a => a
类型)和 1.0
(Fractional a => a
类型)。就像您的示例一样,它工作正常并且类型为 Bar a => a
.
- 数字
1
(技术上代表fromInteger
应用于Integer
值1
)属于class[=14]中的所有类型=]. - class
Fractional
中的所有类型也属于classNum
。
因此,
编号1
属于classFractional
中的所有类型。
类型class非常不像面向对象class,这一点怎么强调也不为过。
特别是,“如果 1.0
变成 Num
” 没有任何意义。 Num
是一种类型 class,而不是一种类型,因此任何东西都不能“变成 Num
”。事实上,在 Haskell 中,没有任何东西会变成其他东西——一切都有一个具体的类型,它是固定的。
现在你问,多态函数是如何工作的?嗯,它被称为 参数 多态是有原因的:看起来是“任意类型 a
” 实际上是 类型参数 。就像函数参数一样,从它们可以在事后更改其值的意义上说,它们不是变量,但从允许函数调用者为 a
– 只要它满足类型-class 约束。
所以在某种意义上,文字 1
是一个函数:它接受一个 类型的参数 a
和 returns 一个值1 :: a
。它要求的是 a
在 class Num
.
然后我们有 (+)
和 1.0
,它们都需要相同的 a
参数。 (+)
再次需要 Num
,没什么新鲜的;但是 1.0
需要 Fractional a
。所以总而言之,1 + 1.0
是一个函数,它接受类型参数 a
的“三个副本”,并且需要
Num a
Num a
Fractional a
– 这也需要Num a
因为Num
是Fractional
. 的超级 class
如果我们真的必须把类型写成
,那就太尴尬了(1 + 1.0) :: (Num a, Num a, Fractional a, Num a) => a
所以允许省略多余的约束,只留下Fractional a
,这意味着其余所有。我们不能做的是只保留 Num
约束之一,因为那并不意味着 Fractional
.