Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Extractor unapply called twice

I just discovered that unapply in my extractor is being called twice for some reason. Anyone know why, and how to avoid it?

val data = List("a","b","c","d","e")

object Uap {
  def unapply( s:String ) = {
    println("S: "+s)
    Some(s+"!")
  }             
}

println( data.collect{ case Uap(x) => x } )

This produces output:

S: a
S: a
S: b
S: b
S: c
S: c
S: d
S: d
S: e
S: e
List(a!, b!, c!, d!, e!)

The final result is fine but in my real program the unapply is non-trivial, so I certainly don't want to call it twice!

like image 316
Greg Avatar asked Dec 09 '25 00:12

Greg


2 Answers

collect takes a PartialFunction as input. PartialFunction defines two key members: isDefinedAt and apply. When collect runs your function, it runs your extractor once to determine if your function isDefinedAt some particular input, and if it is, then it runs the extractor again as part of apply to extract the value.

If there is a trivial way of correctly implementing isDefinedAt, you could implement this yourself by implementing your own PartialFunction explicitly, instead of using the case syntax. or you could do a filter and then map with a total function on the collection (which is essentially what collect is doing by calling isDefinedAt, then apply)

Another option would be to lift the Partial function to a total function. PartialFunction defines lift which turns a PartialFunction[A,B] into a A=>Option[B]. You could use this lifted function (call it fun) to do: data.map(fun).collect { case Some(x) => x }

like image 126
stew Avatar answered Dec 12 '25 00:12

stew


Actually, this was addressed in 2.11 as a performance bug:

$ skala
Welcome to Scala version 2.11.0-20130423-194141-5ec9dbd6a9 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_06).
Type in expressions to have them evaluated.
Type :help for more information.

scala> val data = List("a","b","c","d","e")
data: List[String] = List(a, b, c, d, e)

scala> 

scala> object Uap {
     |   def unapply( s:String ) = {
     |     println("S: "+s)
     |     Some(s+"!")
     |   }             
     | }
defined object Uap

scala> 

scala> println( data.collect{ case Uap(x) => x } )
S: a
S: b
S: c
S: d
S: e
List(a!, b!, c!, d!, e!)

See the efficiency notes on applyOrElse.

Here's a version for 2.10, where the issue is easily remedied by extension:

object Test extends App {
  import scala.collection.TraversableLike
  import scala.collection.generic.CanBuildFrom
  import scala.collection.immutable.StringLike

  implicit class Collector[A, Repr, C <: TraversableLike[A, Repr]](val c: C) extends AnyVal {
    def collecting[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
      val b = bf(c.repr)
      c.foreach(pf.runWith(b += _))
      b.result
    }
  }

  val data = List("a","b","c","d","e")

  object Uap {
    def unapply( s:String ) = {
      println("S: "+s)
      s match {
        case "foo" => None
        case _     => Some(s+"!")
      }
    }
  }
  val c = Collector[String, List[String], List[String]](data)
  Console println c.collecting { case Uap(x) => x }
}

Result:

$ scalac -version
Scala compiler version 2.10.1 -- Copyright 2002-2013, LAMP/EPFL

apm@halyard ~/tmp
$ scalac applyorelse.scala ; scala applyorelse.Test
S: a
S: b
S: c
S: d
S: e
List(a!, b!, c!, d!, e!)

Note that this version of Uap is partial:

scala> val data = List("a","b","c","d","e", "foo")
data: List[String] = List(a, b, c, d, e, foo)

scala> data.map{ case Uap(x) => x }
S: a
S: b
S: c
S: d
S: e
S: foo
scala.MatchError: foo (of class java.lang.String)

I think that if the use case is PF, the code should be partial.

like image 20
som-snytt Avatar answered Dec 12 '25 00:12

som-snytt



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!