Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
959 views
in Technique[技术] by (71.8m points)

scala - Is the PartialFunction design inefficient?

This is something I've wondered about for a while. I see this pattern a lot:

if (pf.isDefinedAt(in)) pf(in)

By breaking this up into two separate calls, all of the patterns that were evaluated in #isDefinedAt are then also evaluated in #apply. For example:

object Ex1 {
  def unapply(in: Int) : Option[String] = {
    println("Ex1")
    if (in == 1) Some("1") else None
  }
}

object Ex2 {
  def unapply(in: Int) : Option[String] = {
    println("Ex2")
    if (in == 2) Some("2") else None
  }
}

val pf : PartialFunction[Int,String] = {
  case Ex1(result) => result
  case Ex2(result) => result
}

val in = 2

if (pf.isDefinedAt(in)) pf(in)

Which prints

Ex1
Ex2
Ex1
Ex2
res52: Any = 2

In the worst case, where your pattern matches last, you've evaluated your patterns/extractors twice when calling a PartialFunction. This could become inefficient when matching over custom extractors that did more than just a simple class or list pattern match (for example, if you had an extractor that parsed an XML document and returned some value objects)

PartialFunction#lift suffers from the same double-evaluation:

scala> pf.lift(2)
Ex1
Ex2
Ex1
Ex2
res55: Option[String] = Some(2)

Is there a way to conditionally call a function if it is defined without potentially calling all of your extractors twice?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

There is a conversation going on about this right now on the scala-internals mailing list. Martin Odersky has suggested a new type: FunctionWithDefault. Martin talks not only of a run-time penalty, but a compile time penalty (of class file bloat) of using PartialFunction:

First, we need to generate the pattern matching code twice, once in the apply and then again in the isDefinedAt. Second, we also need to execute the code twice, first to test whether the function is applicable, and then to actually apply it.

The answer to your question is essentially "yes" and and this behaviour (of PartialFunction) will not change either due to backwards-compatibility issues (for example, what if the isDefinedAt is side-effecting).

The new type being proposed, FunctionWithDefault has no isDefinedAt and has a method:

trait FunctionWithDefault[-I, +O] {
  def applyOrElse[OO >: O](i : I, default : I => OO) : OO
}

which acts a bit like Options getOrElse method.

I have to say that, as usual, I cannot imagine this inefficiency poses any sort of performance problem in the overwhelming majority of cases.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...