为什么 Num 可以像分数一样? [英] Why can a Num act like a Fractional?
问题描述
正如预期的那样,这很好用:
As expected, this works fine:
foo :: Fractional a => a
foo = undefined -- datum
bar :: Num a => a -> a
bar a = undefined -- function
baz :: Fractional a => a
baz = bar foo -- application
这按预期工作,因为每个 Fractional
也是一个 Num
.
This works as expected because every Fractional
is also a Num
.
正如预期的那样,我们可以将 Fractional
参数传递给 Num
参数.
So as expected, we can pass a Fractional
argument into a Num
parameter.
另一方面,以下也有效.我不明白为什么.
On the other hand, the following works too. I don't understand why.
foo :: Fractional a => a -> a
foo a = undefined -- function
bar :: Num a => a
bar = undefined -- datum
baz :: Fractional a => a
baz = foo bar -- application
出乎意料地工作!有 Num
不是 Fractionals
.
Works unexpectedly! There are Num
s that are not Fractionals
.
那么为什么我可以将 Num
参数传递给 Fractional
参数?你能解释一下吗?
So why can I pass a Num
argument into a Fractional
parameter? Can you explain?
推荐答案
chi 的回答对正在发生的事情提供了很好的高级解释.我认为给出一个稍微低级(但也更机械)的方式来理解这一点也可能很有趣,这样你就可以解决其他类似的问题,转动曲柄并得到正确的答案.我将把类型作为一种类型的值的用户和实现者之间的协议来讨论.
chi's answer gives a great high-level explanation of what's happening. I thought it might also be fun to give a slightly more low-level (but also more mechanical) way to understand this, so that you might be able to approach other similar problems, turn a crank, and get the right answer. I'm going to talk about types as a sort of protocol between the user of the value of that type and the implementer.
- 对于
forall a.t
,调用者可以选择一种类型,然后他们继续使用协议t
(其中a
在t
). - 对于
Foo a =>t
,调用者必须向实现者提供a
是Foo
的实例的证据.然后他们继续使用协议t
. - 对于
t1 ->t2
,调用者可以选择t1
类型的值(例如,通过运行协议t1
并切换实现者和调用者的角色).然后他们继续使用协议t2
. - 对于任何类型
t
(即在任何时间),实现者可以通过生成适当类型的值来缩短协议.如果以上规则都不适用(例如,如果我们已经达到了像Int
这样的基本类型或像a
这样的裸类型变量),则实现者必须 这样做.
- For
forall a. t
, the caller gets to choose a type, then they continue with protocolt
(wherea
has been replaced with the caller's choice everywhere int
). - For
Foo a => t
, the caller must provide proof to the implementer thata
is an instance ofFoo
. Then they continue with protocolt
. - For
t1 -> t2
, the caller gets to choose a value of typet1
(e.g. by running protocolt1
with the roles of implementer and caller switched). Then they continue with protocolt2
. - For any type
t
(that is, at any time), the implementer can cut the protocol short by just producing a value of the appropriate type. If none of the rules above apply (e.g. if we have reached a base type likeInt
or a bare type variable likea
), the implementer must do so.
现在让我们为您的术语指定一些不同的名称,以便我们可以区分它们:
Now let's give some distinct names to your terms so we can differentiate them:
valFrac :: forall a. Fractional a => a
valNum :: forall a. Num a => a
idFrac :: forall a. Fractional a => a -> a
idNum :: forall a. Num a => a -> a
我们还有两个定义要探索:
We also have two definitions we want to explore:
applyIdNum :: forall a. Fractional a => a
applyIdNum = idNum valFrac
applyIdFrac :: forall a. Fractional a => a
applyIdFrac = idFrac valNum
先说applyIdNum
.协议说:
- 来电者选择类型
a
. - 调用者证明它是
Fractional
. - Implementer 提供
a
类型的值.
- Caller chooses a type
a
. - Caller proves it is
Fractional
. - Implementer provides a value of type
a
.
实现说明:
Implementer 作为调用者启动
idNum
协议.所以,她必须:
Implementer starts the
idNum
protocol as the caller. So, she must:
- 选择类型
a
.她悄悄地做出了与她来电者相同的选择. - 证明
a
是Num
的一个实例.这没问题,因为她实际上知道a
是Fractional
,这意味着Num
. - 提供
a
类型的值.在这里,她选择了valFrac
.为了完整起见,她必须证明valFrac
的类型为a
.
- Choose a type
a
. She quietly makes the same choice as her caller did. - Prove that
a
is an instance ofNum
. This is no problem, because she actually knows thata
isFractional
, and this impliesNum
. - Provide a value of type
a
. Here she choosesvalFrac
. To be complete, she must then show thatvalFrac
has the typea
.
所以实现者现在运行 valFrac
协议.她:
- 选择类型
a
.在这里,她悄悄地选择了idNum
期望的类型,这恰好与她的调用者为a
选择的类型相同. - 证明
a
是Fractional
的一个实例.她使用与来电者相同的证据. valFrac
的实现者随后承诺根据需要提供a
类型的值.
- Chooses a type
a
. Here she quietly chooses the type thatidNum
is expecting, which happens to coincidentally be the same as the type that her caller chose fora
. - Prove that
a
is an instance ofFractional
. She uses the same proof her caller did. - The implementer of
valFrac
then promises to provide a value of typea
, as needed.
为了完整起见,这里是 applyIdFrac
的类似讨论.协议说:
For completeness, here is the analogous discussion for applyIdFrac
. The protocol says:
- 来电者选择类型
a
. - 调用者证明
a
是Fractional
. - 实现者必须提供
a
类型的值.
- Caller chooses a type
a
. - Caller proves that
a
isFractional
. - Implementer must provide a value of type
a
.
实现说明:
Implementer 将执行
idFrac
协议.所以,她必须:
Implementer will execute the
idFrac
protocol. So, she must:
- 选择类型.在这里,她静静地选择来电者的选择.
- 证明
a
是Fractional
.她传递了来电者的证据. - 选择
a
类型的值.她将执行valNum
协议来做到这一点;并且我们必须检查这是否产生了a
类型的值.
- Choose a type. Here she quietly chooses whatever her caller chose.
- Prove that
a
isFractional
. She passes on her caller's proof of this. - Choose a value of type
a
. She will execute thevalNum
protocol to do this; and we must check that this produces a value of typea
.
在valNum
协议的执行过程中,她:
During the execution of the valNum
protocol, she:
- 选择一种类型.这里她选择
idFrac
期望的类型,即a
;这也恰好是她的来电者选择的类型. - 证明
Num a
成立.这是她可以做的,因为她的调用者提供了Fractional a
的证明,并且您可以从Fractional a
的证明中提取Num a
的证明>. valNum
的实现者然后根据需要提供a
类型的值.
- Chooses a type. Here she chooses the type that
idFrac
expects, namelya
; this also happens to be the type her caller chose. - Prove that
Num a
holds. This she can do, because her caller supplied a proof thatFractional a
, and you can extract a proof ofNum a
from a proof ofFractional a
. - The implementer of
valNum
then provides a value of typea
, as needed.
有了场地上的所有细节,我们现在可以尝试缩小并查看大图.applyIdNum
和 applyIdFrac
具有相同的类型,即 forall a.分数 a =>一个
.因此,两种情况下的实现者都假设 a
是 Fractional
的一个实例.但是由于所有 Fractional
实例都是 Num
实例,这意味着实现者可以假设 Fractional
和 Num
都适用.这使得使用假定实现中的任一约束的函数或值变得容易.
With all the details on the field, we can now try to zoom out and see the big picture. Both applyIdNum
and applyIdFrac
have the same type, namely forall a. Fractional a => a
. So the implementer in both cases gets to assume that a
is an instance of Fractional
. But since all Fractional
instances are Num
instances, this means the implementer gets to assume both Fractional
and Num
apply. This makes it easy to use functions or values that assume either constraint in the implementation.
附言我在 forall a 中反复使用副词安静地"来选择所需的类型.t
协议.这是因为 Haskell 非常努力地向用户隐藏这些选择.但是如果你喜欢 TypeApplications
扩展,你可以使它们显式;在协议f
中选择类型t
使用语法f @t
.不过,实例证明仍会以您的名义进行静默管理.
P.S. I repeatedly used the adverb "quietly" for choices of types needed during the forall a. t
protocol. This is because Haskell tries very hard to hide these choices from the user. But you can make them explicit if you like with the TypeApplications
extension; choosing type t
in protocol f
uses the syntax f @t
. Instance proofs are still silently managed on your behalf, though.
这篇关于为什么 Num 可以像分数一样?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!