为什么用提取器替换我的Scala案例类会破坏我的高阶函数?

时间:2011-01-23 21:10:17

标签: scala

假设我有一个包装整数的简单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上的提取器和模式上的模式看起来完全相同,所以客户端中的模式匹配将继续有效。

当然非常正确,但如果他们使用案例类名称作为函数,我们将打破我们的客户。因为这样做可以实现强大的抽象,所以肯定会有。

因此,当将它们传递给更高阶函数时,我们如何使提取器的行为与案例类相同?

2 个答案:

答案 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) )

我还将参数设置为Wrapperval并在参数中交换并在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] {