为什么 Num 可以像分数一样? [英] Why can a Num act like a Fractional?

查看:27
本文介绍了为什么 Num 可以像分数一样?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

正如预期的那样,这很好用:

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 Nums 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(其中 at).
  • 对于 Foo a =>t,调用者必须向实现者提供aFoo 的实例的证据.然后他们继续使用协议 t.
  • 对于 t1 ->t2,调用者可以选择 t1 类型的值(例如,通过运行协议 t1 并切换实现者和调用者的角色).然后他们继续使用协议 t2.
  • 对于任何类型t(即在任何时间),实现者可以通过生成适当类型的值来缩短协议.如果以上规则都不适用(例如,如果我们已经达到了像 Int 这样的基本类型或像 a 这样的裸类型变量),则实现者必须 这样做.
  • For forall a. t, the caller gets to choose a type, then they continue with protocol t (where a has been replaced with the caller's choice everywhere in t).
  • For Foo a => t, the caller must provide proof to the implementer that a is an instance of Foo. Then they continue with protocol t.
  • For t1 -> t2, the caller gets to choose a value of type t1 (e.g. by running protocol t1 with the roles of implementer and caller switched). Then they continue with protocol t2.
  • 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 like Int or a bare type variable like a), 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.协议说:

  1. 来电者选择类型a.
  2. 调用者证明它是Fractional.
  3. Implementer 提供 a 类型的值.
  1. Caller chooses a type a.
  2. Caller proves it is Fractional.
  3. Implementer provides a value of type a.

实现说明:

  1. Implementer 作为调用者启动 idNum 协议.所以,她必须:

  1. Implementer starts the idNum protocol as the caller. So, she must:

  1. 选择类型a.她悄悄地做出了与来电者相同的选择.
  2. 证明aNum的一个实例.这没问题,因为她实际上知道aFractional,这意味着Num.
  3. 提供 a 类型的值.在这里,她选择了 valFrac.为了完整起见,她必须证明 valFrac 的类型为 a.
  1. Choose a type a. She quietly makes the same choice as her caller did.
  2. Prove that a is an instance of Num. This is no problem, because she actually knows that a is Fractional, and this implies Num.
  3. Provide a value of type a. Here she chooses valFrac. To be complete, she must then show that valFrac has the type a.

  • 所以实现者现在运行 valFrac 协议.她:

    1. 选择类型a.在这里,她悄悄地选择了 idNum 期望的类型,这恰好与她的调用者为 a 选择的类型相同.
    2. 证明aFractional 的一个实例.她使用与来电者相同的证据.
    3. valFrac 的实现者随后承诺根据需要提供 a 类型的值.
    1. Chooses a type a. Here she quietly chooses the type that idNum is expecting, which happens to coincidentally be the same as the type that her caller chose for a.
    2. Prove that a is an instance of Fractional. She uses the same proof her caller did.
    3. The implementer of valFrac then promises to provide a value of type a, as needed.

  • 为了完整起见,这里是 applyIdFrac 的类似讨论.协议说:

    For completeness, here is the analogous discussion for applyIdFrac. The protocol says:

    1. 来电者选择类型a.
    2. 调用者证明aFractional.
    3. 实现者必须提供 a 类型的值.
    1. Caller chooses a type a.
    2. Caller proves that a is Fractional.
    3. Implementer must provide a value of type a.

    实现说明:

    1. Implementer 将执行 idFrac 协议.所以,她必须:

    1. Implementer will execute the idFrac protocol. So, she must:

    1. 选择类型.在这里,她静静地选择来电者的选择.
    2. 证明aFractional.她传递了来电者的证据.
    3. 选择 a 类型的值.她将执行 valNum 协议来做到这一点;并且我们必须检查这是否产生了 a 类型的值.
    1. Choose a type. Here she quietly chooses whatever her caller chose.
    2. Prove that a is Fractional. She passes on her caller's proof of this.
    3. Choose a value of type a. She will execute the valNum protocol to do this; and we must check that this produces a value of type a.

  • valNum协议的执行过程中,她:

  • During the execution of the valNum protocol, she:

    1. 选择一种类型.这里她选择idFrac期望的类型,即a;这也恰好是她的来电者选择的类型.
    2. 证明 Num a 成立.这是她可以做的,因为她的调用者提供了 Fractional a 的证明,并且您可以从 Fractional a 的证明中提取 Num a 的证明>.
    3. valNum 的实现者然后根据需要提供 a 类型的值.
    1. Chooses a type. Here she chooses the type that idFrac expects, namely a; this also happens to be the type her caller chose.
    2. Prove that Num a holds. This she can do, because her caller supplied a proof that Fractional a, and you can extract a proof of Num a from a proof of Fractional a.
    3. The implementer of valNum then provides a value of type a, as needed.

  • 有了场地上的所有细节,我们现在可以尝试缩小并查看大图.applyIdNumapplyIdFrac 具有相同的类型,即 forall a.分数 a =>一个.因此,两种情况下的实现者都假设 aFractional 的一个实例.但是由于所有 Fractional 实例都是 Num 实例,这意味着实现者可以假设 FractionalNum 都适用.这使得使用假定实现中的任一约束的函数或值变得容易.

    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屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆