假设我有一个包装整数的简单case类,以及一个接受带有整数到包装器的函数的高阶方法。
case class Wrapper(x :Int)
def higherOrder(f : Int => Wrapper) = println(f(42))
然后,我可以调用高阶函数,传入包装器生成的apply函数。令人惊讶的是,我也可以传递包装名称。
higherOrder(Wrapper.apply) // okay
higherOrder(Wrapper) // okay, wow!
这真的很酷。它允许我们将case类的名称视为一个函数,它可以促进表达式抽象。有关这种凉爽的一个例子,请参阅这里的答案。 What does "abstract over" mean?
现在假设我的case类不够强大,我需要创建一个提取器。作为一个有点人为的用例,假设我需要能够对解析为整数的字符串进行模式匹配。
// Replace Wrapper case class with an extractor
object Wrapper {
def apply(x :Int) = new Wrapper(x)
def unapply(s :String) :Option[Wrapper] = {
// details elided
}
}
class Wrapper(x :Int) {
override def toString = "Wrapper(" + x + ")"
// other methods elided
}
在这个变化下,我仍然可以将Wrapper.apply传递给我的高阶函数,但只是传递Wrapper不再有效。
higherOrder(Wrapper.apply) // still okay
higherOrder(Wrapper) // DOES NOT COMPILE
^^^^^^^
// type mismatch; found : Wrapper.type (with underlying type Wrapper)
// required: (Int) => Wrapper
哎哟!这就是为什么这种不对称令人不安的原因。 Odersky,Spoon和Venners的建议(Scala编程,第500页)说
您总是可以从案例类开始,然后,如果需要,可以更改为提取器。因为Scala上的提取器和模式上的模式看起来完全相同,所以客户端中的模式匹配将继续有效。
当然非常正确,但如果他们使用案例类名称作为函数,我们将打破我们的客户。因为这样做可以实现强大的抽象,所以肯定会有。
因此,当将它们传递给更高阶函数时,我们如何使提取器的行为与案例类相同?
答案 0 :(得分:12)
简单地说,Wrapper
(对象)不是一个Function,它只是一个带有apply方法的对象。这与对象也是一个提取器完全无关。
试试这个:
class Wrapper(val x: Int) {
override def toString = "Wrapper(" + x + ")"
// other methods elided
}
object Wrapper extends (Int => Wrapper) {
def apply(x: Int) = new Wrapper(x)
def unapply(w: Wrapper): Option[Int] = Some(w.x)
}
def higherOrder(f: Int => Wrapper) = println( f(42) )
我还将参数设置为Wrapper
和val
并在参数中交换并在unapply
的定义中返回值,以便它与案例类行为相匹配。
答案 1 :(得分:8)
Scala提取器伴随对象应该扩展Function
对于案例类,编译器会在后台生成一个伴随对象。这包含Wrapper案例类的相关静态方法,包括apply。对着字节码,我们发现Wrapper的伴随对象扩展了Function1。 (实际上它扩展了AbstractFunction1,它使用@specialized来避免自动装箱。)
这里也会注意到这一点。 Why do case class companion objects extend FunctionN?
当用提取器替换我们的Wrapper案例类时,我们应该注意使用AbstractFunction1扩展我们的本地增长的伴随对象,以保持我们客户的向后兼容性。
这相当于源代码中的一个小调整。 apply方法根本不会改变。
object Wrapper extends scala.runtime.AbstractFunction1[Int, Wrapper] {