在研究如何在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 {
的结尾。所以,如果这更详细地输入,也许我会理解正在如何构建备忘录的“跳跃”。
对此非常感谢。谢谢。
答案 0 :(得分:14)
A => B
是Function1[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编译器有许多方便(但也是高度交织)的假设。
extends (A => B)
视为extends Function1[A, B]
(ScalaDoc for Function1[+T1, -R])的同义词apply(x: A): B
的具体实现; def apply(x: A): B = cache.getOrElseUpdate(x, f(x))
match
= Memo {
{}
之间的内容作为参数传递给Memo案例类构造函数{}
之间的隐含类型为PartialFunction[Int, BigInt]
,并且编译器使用“匹配”代码块作为PartialFunction方法的apply()
的覆盖,然后提供PartialFunction的方法isDefinedAt()
的附加覆盖。 <强>详细信息:强>
定义案例类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
方法的其他人,从而可以完成一些工作。