为什么Num可以表现为小数? [英] Why can a Num act like a Fractional?

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

问题描述

按预期,这可以正常工作:

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(其中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.该协议说:

Let's talk about applyIdNum first. The protocol says:

  1. 呼叫者选择类型a.
  2. 呼叫者证明是Fractional.
  3. 实施程序提供的类型为a的值.
  1. Caller chooses a type a.
  2. Caller proves it is Fractional.
  3. Implementer provides a value of type a.

实施说明:

  1. 实现者以调用者身份启动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协议.她:

  • So the implementer now runs the valFrac protocol. She:

    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. 实现者将执行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. Fractional 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.

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

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