简体   繁体   English

在Scala中,案例类的“扩展(A => B)”是什么意思?

[英]In Scala, what does “extends (A => B)” on a case class mean?

In researching how to do Memoization in Scala, I've found some code I didn't grok. 在研究如何在Scala中进行备忘时,我发现了一些我不喜欢的代码。 I've tried to look this particular "thing" up, but don't know by what to call it; 我试图查找这种特殊的“事物”,但是不知道用什么来称呼它。 ie the term by which to refer to it. 即引用它的术语。 Additionally, it's not easy searching using a symbol, ugh! 此外,使用符号搜索也不容易!

I saw the following code to do memoization in Scala here : 我在这里看到了以下代码可在Scala中进行记忆化:

case class Memo[A,B](f: A => B) extends (A => B) {
  private val cache = mutable.Map.empty[A, B]
  def apply(x: A) = cache getOrElseUpdate (x, f(x))
}

And it's what the case class is extending that is confusing me, the extends (A => B) part. extends (A => B)部分正是使案例类扩展的东西。 First, what is happening? 首先,发生了什么事? Secondly, why is it even needed? 其次,为什么还需要它? And finally, what do you call this kind of inheritance; 最后,您怎么称这种继承? ie is there some specific name or term I can use to refer to it? 即是否可以使用一些特定的名称或术语来指代它?

Next, I am seeing Memo used in this way to calculate a Fibanocci number here : 接下来,我看到这样计算Fibanocci号码使用备注这里

  val fibonacci: Memo[Int, BigInt] = Memo {
    case 0 => 0
    case 1 => 1
    case n => fibonacci(n-1) + fibonacci(n-2)
  }

It's probably my not seeing all of the "simplifications" that are being applied. 我可能没有看到所有正在应用的“简化”。 But, I am not able to figure out the end of the val line, = Memo { . 但是,我不知道val行的结尾= Memo { So, if this was typed out more verbosely, perhaps I would understand the "leap" being made as to how the Memo is being constructed. 因此,如果更详细地输入,也许我会理解备忘录的构造方式正在“飞跃”。

Any assistance on this is greatly appreciated. 在这方面的任何帮助将不胜感激。 Thank you. 谢谢。

A => B is short for Function1[A, B] , so your Memo extends a function from A to B , most prominently defined through method apply(x: A): B which must be defined. A => BFunction1[A, B]缩写,因此您的Memo将函数从A扩展到B ,最明显的是通过必须定义的apply(x: A): B方法定义的。

Because of the "infix" notation, you need to put parentheses around the type, ie (A => B) . 由于使用了“中缀”符号,因此需要在类型周围加上括号,即(A => B) You could also write 你也可以写

case class Memo[A, B](f: A => B) extends Function1[A, B] ...

or 要么

case class Memo[A, B](f: Function1[A, B]) extends Function1[A, B] ...

To complete 0_'s answer, fibonacci is being instanciated through the apply method of Memo 's companion object, generated automatically by the compiler since Memo is a case class. 为了完成0_的答案, fibonacci通过Memo的伴随对象的apply方法实例化,由于Memo是case类,因此编译器会自动生成该对象。

This means that the following code is generated for you: 这意味着将为您生成以下代码:

object Memo {
  def apply[A, B](f: A => B): Memo[A, B] = new Memo(f)
}

Scala has special handling for the apply method: its name needs not be typed when calling it. Scala对apply方法有特殊的处理方式:调用它的名称时不需要键入其名称。 The two following calls are strictly equivalent: 以下两个调用严格等效:

Memo((a: Int) => a * 2)

Memo.apply((a: Int) => a * 2)

The case block is known as pattern matching. case块称为模式匹配。 Under the hood, it generates a partial function - that is, a function that is defined for some of its input parameters, but not necessarily all of them. 在引擎盖下,它生成一个部分函数-即为其某些输入参数(但不一定是所有输入参数)定义的函数。 I'll not go in the details of partial functions as it's beside the point ( this is a memo I wrote to myself on that topic, if you're keen), but what it essentially means here is that the case block is in fact an instance of PartialFunction . 我不会详细介绍局部函数(因为这很重要, 是我就该主题写给自己的备忘录),但是从本质上讲,这意味着case块实际上是PartialFunction的一个实例。

If you follow that link, you'll see that PartialFunction extends Function1 - which is the expected argument of Memo.apply . 如果您PartialFunction该链接,将会看到PartialFunction扩展了Function1-这是Memo.apply的预期参数。

So what that bit of code actually means, once desugared (if that's a word), is: 因此,一旦删除(如果是一个单词),那段代码实际上意味着的是:

lazy val fibonacci: Memo[Int, BigInt] = Memo.apply(new PartialFunction[Int, BigInt] {
  override def apply(v: Int): Int =
    if(v == 0)      0
    else if(v == 1) 1
    else            fibonacci(v - 1) + fibonacci(v - 2)

  override isDefinedAt(v: Int) = true
})

Note that I've vastly simplified the way the pattern matching is handled, but I thought that starting a discussion about unapply and unapplySeq would be off topic and confusing. 请注意,我已经大大简化了模式匹配的处理方式,但我认为在开始讨论关于unapplyunapplySeq是题外话和混乱。

I am the original author of doing memoization this way. 我是以此方式进行记忆的原始作者。 You can see some sample usages in that same file. 您可以在同一文件中看到一些示例用法。 It also works really well when you want to memoize on multiple arguments too because of the way Scala unrolls tuples: 当您也想记住多个参数时,由于Scala展开元组的方式,它也非常有效:

    /**
     * @return memoized function to calculate C(n,r) 
     * see http://mathworld.wolfram.com/BinomialCoefficient.html
     */
     val c: Memo[(Int, Int), BigInt] = Memo {
        case (_, 0) => 1
        case (n, r) if r > n/2 => c(n, n-r)
        case (n, r) => c(n-1, r-1) + c(n-1, r)
     }
     // note how I can invoke a memoized function on multiple args too
     val x = c(10, 3) 

This answer is a synthesis of the partial answers provided by both 0__ and Nicolas Rinaudo. 该答案是0__和Nicolas Rinaudo提供的部分答案的综合。

Summary: 摘要:

There are many convenient (but also highly intertwined) assumptions being made by the Scala compiler. Scala编译器有许多方便的假设(但也是如此)。

  1. Scala treats extends (A => B) as synonymous with extends Function1[A, B] ( ScalaDoc for Function1[+T1, -R] ) Scala将extends (A => B)extends Function1[A, B]同义( Function1 [+ T1,-R]的ScalaDoc
  2. A concrete implementation of Function1's inherited abstract method apply(x: A): B must be provided; 必须提供Function1继承的抽象方法apply(x: A): B的具体实现; def apply(x: A): B = cache.getOrElseUpdate(x, f(x))
  3. Scala assumes an implied match for the code block starting with = Memo { 阶假定一个隐含的match的码块开头= Memo {
  4. Scala passes the content between {} started in item 3 as a parameter to the Memo case class constructor Scala将第3项中{}之间的内容作为参数传递给Memo case类构造函数
  5. Scala assumes an implied type between {} started in item 3 as PartialFunction[Int, BigInt] and the compiler uses the "match" code block as the override for the PartialFunction method's apply() and then provides an additional override for the PartialFunction's method isDefinedAt() . Scala假定在第3项中的{}之间为隐式类型,为PartialFunction[Int, BigInt] ,并且编译器使用“ match”代码块作为PartialFunction方法的apply()的替代,然后为PartialFunction的方法isDefinedAt()提供其他替代isDefinedAt()

Details: 细节:

The first code block defining the case class Memo can be written more verbosely as such: 定义案例类Memo的第一个代码块可以这样写:

case class Memo[A,B](f: A => B) extends Function1[A, B] {    //replaced (A => B) with what it's translated to mean by the Scala compiler
  private val cache = mutable.Map.empty[A, B]
  def apply(x: A): B = cache.getOrElseUpdate(x, f(x))  //concrete implementation of unimplemented method defined in parent class, Function1
}

The second code block defining the val fibanocci can be written more verbosely as such: 可以更详细地编写定义val fibanocci的第二个代码块,如下所示:

lazy val fibonacci: Memo[Int, BigInt] = {
  Memo.apply(
    new PartialFunction[Int, BigInt] {
      override def apply(x: Int): BigInt = {
        x match {
          case 0 => 0
          case 1 => 1
          case n => fibonacci(n-1) + fibonacci(n-2)
        }
      }
      override def isDefinedAt(x: Int): Boolean = true
    }
  )
}

Had to add lazy to the second code block's val in order to deal with a self-referential problem in the line case n => fibonacci(n-1) + fibonacci(n-2) . 为了在行case n => fibonacci(n-1) + fibonacci(n-2)处理自指问题,必须向第二个代码块的val添加lazy

And finally, an example usage of fibonacci is: 最后,斐波那契的用法示例是:

val x:BigInt = fibonacci(20) //returns 6765 (almost instantly)

One more word about this extends (A => B) : the extends here is not required, but necessary if the instances of Memo are to be used in higher order functions or situations alike. 关于此extends (A => B)一句话extends (A => B) :这里的extends不是必需的,但是如果要在更高阶的函数或情况下使用Memo的实例,则必须这样做。

Without this extends (A => B) , it's totally fine if you use the Memo instance fibonacci in just method calls. 没有这种extends (A => B) ,如果仅在方法调用中使用Memo实例fibonacci ,那就完全可以了。

case class Memo[A,B](f: A => B) {
    private val cache = scala.collection.mutable.Map.empty[A, B]
    def apply(x: A):B = cache getOrElseUpdate (x, f(x))
}
val fibonacci: Memo[Int, BigInt] = Memo {
    case 0 => 0
    case 1 => 1
    case n => fibonacci(n-1) + fibonacci(n-2)
}

For example: 例如:

Scala> fibonacci(30)
res1: BigInt = 832040

But when you want to use it in higher order functions, you'd have a type mismatch error. 但是,当您想在高阶函数中使用它时,会出现类型不匹配错误。

Scala> Range(1, 10).map(fibonacci)
<console>:11: error: type mismatch;
 found   : Memo[Int,BigInt]
 required: Int => ?
              Range(1, 10).map(fibonacci)
                               ^

So the extends here only helps to ID the instance fibonacci to others that it has an apply method and thus can do some jobs. 因此,这里的extends仅有助于将实例fibonacci标识给其他实例,因为它具有apply方法,因此可以完成一些工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM