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

时间:2013-10-23 17:12:26

标签: scala map memoization

在研究如何在Scala中进行Memoization时,我发现了一些我没有理解的代码。我试图看看这个特别的“东西”,但不知道该怎么称呼它;即引用它的术语。另外,用符号搜索并不容易,唉!

我在Scala here中看到以下代码进行记忆:

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))
}

正是案例类的扩展让我感到困惑,extends (A => B)部分。首先,发生了什么?其次,为什么甚至需要呢?最后,你称之为这种继承;即是否有一些特定的名称或术语可供我参考?

接下来,我看到Memo用这种方式来计算Fibanocci数here

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

可能我没有看到所有正在应用的“简化”。但是,我无法弄清楚val= Memo {的结尾。所以,如果这更详细地输入,也许我会理解正在如何构建备忘录的“跳跃”。

对此非常感谢。谢谢。

5 个答案:

答案 0 :(得分:14)

A => BFunction1[A, B]的缩写,因此您的Memo将函数从A扩展为B,最突出的是通过方法apply(x: A): B定义的必须定义。

由于“中缀”符号,您需要在类型周围添加括号,即(A => B)。你也可以写

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

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

答案 1 :(得分:6)

要完成0_的回答,fibonacci通过Memo的伴随对象的apply方法进行实例化,由编译器自动生成,因为Memo是一个案例类。

这意味着您将为您生成以下代码:

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

Scala对apply方法有特殊处理:调用它时不需要输入其名称。以下两个电话严格相同:

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

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

case块称为模式匹配。在引擎盖下,它会生成一个部分函数 - 即为某些输入参数定义的函数,但不一定是所有函数。我不会详细介绍部分功能,因为它不是重点(this是我写给自己的备忘录,如果你热衷的话),但它在本质上意味着PartialFunction { {1}}块实际上是Function1的实例。

如果您点击该链接,则会看到case延伸{{3}} - 这是PartialFunction的预期参数。

那么这段代码实际上意味着什么,一旦被贬低(如果这是一个词),是:

Memo.apply

请注意,我已经极大地简化了模式匹配的处理方式,但我认为开始讨论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 }) unapply将会脱离主题并引起混淆。

答案 2 :(得分:5)

我是这样做备忘录的original作者。您可以在同一个文件中看到一些示例用法。当你想要记住多个参数时,由于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) 

答案 3 :(得分:3)

这个答案是0__和Nicolas Rinaudo提供的部分答案的综合。

<强>要点:

Scala编译器有许多方便(但也是高度交织)的假设。

  1. Scala将extends (A => B)视为extends Function1[A, B]ScalaDoc for Function1[+T1, -R])的同义词
  2. 必须提供Function1的继承抽象方法apply(x: A): B的具体实现; def apply(x: A): B = cache.getOrElseUpdate(x, f(x))
  3. Scala假定以match
  4. 开头的代码块隐含= Memo {
  5. Scala将项目3中启动的{}之间的内容作为参数传递给Memo案例类构造函数
  6. Scala假设在项目3中开始的{}之间的隐含类型为PartialFunction[Int, BigInt],并且编译器使用“匹配”代码块作为PartialFunction方法的apply()的覆盖,然后提供PartialFunction的方法isDefinedAt()的附加覆盖。
  7. <强>详细信息:

    定义案例类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
    }
    

    定义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
        }
      )
    }
    

    必须将lazy添加到第二个代码块的val中才能处理行case n => fibonacci(n-1) + fibonacci(n-2)中的自引用问题。

    最后,斐波那契的一个例子是:

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

答案 4 :(得分:2)

关于此extends (A => B)的另外一个词:此处extends不是必需的,但如果Memo的实例要用于更高阶函数或类似情况,则是必需的。

如果不使用此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)
}

例如:

Scala> fibonacci(30)
res1: BigInt = 832040

但是当你想在高阶函数中使用它时,你会遇到类型不匹配错误。

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

所以这里的extends只会帮助将实例fibonacci标识给其拥有apply方法的其他人,从而可以完成一些工作。