为什么Num可以表现为小数? [英] Why can a Num act like a Fractional?
问题描述
按预期,这可以正常工作:
As expected, this works fine:
valFrac :: Fractional a => a
valFrac = undefined
fNum :: Num a => a -> a
fNum a = undefined
resFrac :: Fractional a => a
resFrac = fNum valFrac -- Works as expected because every
-- Fractional is also a 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.
fFrac :: Fractional a => a -> a
fFrac a = undefined
valNum :: Num a => a
valNum = undefined
valFrac :: Fractional a => a
valFrac = fFrac valNum -- Works unexpectedly! There are
-- Nums that are not Fractionals.
-- So why can I pass a Num argument
-- into a Fractional parameter?
问题在注释中.你能解释一下吗?
The question is in the comments. 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
.该协议说:
Let's talk about applyIdNum
first. The protocol says:
- 呼叫者选择类型
a
. - 呼叫者证明是
Fractional
. - 实施程序提供的类型为
a
的值.
- Caller chooses a type
a
. - Caller proves it is
Fractional
. - Implementer provides a value of type
a
.
实施说明:
-
实现者以调用者身份启动
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
协议.她:
So the implementer now runs the valFrac
protocol. She:
- 选择类型
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
.
实施说明:
-
实现者将执行
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. Fractional 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.
P.S.我反复使用副词安静地"来选择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屋!