20

I like to have my code quite naively readable.

If I set up a simple list of tuples:

scala> val a = List(6, 8, 10)
a: List[Int] = List(6, 8, 10)

scala> val b = a zipWithIndex
b: List[(Int, Int)] = List((6,0), (8,1), (10,2))

I'd like to map() on the List, but I find the ._1 ._2 syntax a bit hard-to-read:

scala> val c = b map ( a => if(a._1 > 8) a._1 else a._2 )           
c: List[Int] = List(0, 1, 10)

To 'name' the tuple, I've used:

scala> val c = b map ( { case (num, i) => if(num > 8) num else i } )
c: List[Int] = List(0, 1, 10)

Two questions:

1) Is there a more concise way to name the tuple members?

2) Is there a considerable performance hit for my version above (it is used in moderately performance-critical code).

Thanks.

flend
  • 411
  • 1
  • 4
  • 10
  • 8
    Just a little comment, you can leave the parentheses from the last line: `val c = b map { case (num, i) => if(num > 8) num else i }` – vertti Apr 02 '12 at 16:00
  • 2
    It doesn't really get much more concise than the first option. In terms of clarity, the second option is best, but that `case` keyword implies use of pattern matching, which _does_ incur a performance overhead that is not occurred when not using pattern matching (like in your first example). However, the overhead with pattern matching is not one that I've ever heard of actually causing performance problems for people. – Destin Apr 02 '12 at 16:32
  • 1
    out of curiosity I did a little benchmarking (added to my answer), and I can't find differences between the versions of map with and without pattern matching – Paolo Falabella Apr 03 '12 at 13:53

2 Answers2

20
b map Function.tupled((num, i) => if(num > 8) num else i)

avoids pattern matching and for-expressions so should be reasonably performant. I'd normally just use case as you did though.

Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
  • Thanks. I think this is the best answer, since it suggests a higher performance (albeit complex!) version with named tuple arguments. – flend Apr 03 '12 at 08:07
  • 2
    You can import `Function.tupled` if you want to make this a bit shorter. Or if you want to win an obfuscated Scala contest, import it as a symbol! `import Function.{tupled => *}` Now you can write `b map *((num, i) => if(num > 8) num else i)` – Luigi Plinge Apr 07 '12 at 12:33
8

In this case you might find the equivalent for-comprehension syntax more readable, but it's really a matter of taste...

for {(num, i) <- b} yield if(num >8) num else i

FWIW, I've tried benchmarking the map with and without pattern matching and I got pretty much the same execution time.

Code I've used:

object bench extends scala.testing.Benchmark {
    var b:List[(Int, Int)] = _

    override def setUp {
        val a = (1000000 to 2000000).toList
        b = a zipWithIndex
    }

    def run = b map ( a => if(a._1 > 8) a._1 else a._2 )
 }

I've also created another application with a bench1 object which has only the version of map with the pattern matching instead of the ._1 and ._2. Results on my oldish netbook (scala 2.9.1, xubuntu 11.10):

$ scala bench 10 
bench$   750    758 731 721 733 736 725 743 735 736
$ scala bench1 10
bench1$  774    772 740 724 745 730 711 739 740 740
Paolo Falabella
  • 24,914
  • 3
  • 72
  • 86
  • Agreed, pattern matching overhead from using `case` is tiny, ~1%. But using a `for`-expression adds about 500%! – Luigi Plinge Apr 07 '12 at 12:37
  • @LuigiPlinge `for(elem <- elems) yield foo(elem)` is syntactic sugar for `elems.map(elem => foo(elem))`, so my version should be exactly equivalent to the one with `map` (see [this question](http://stackoverflow.com/questions/9891407/getting-the-desugared-part-of-a-scala-for-comprehension-expression) for instance). Probably there are faster versions that avoid `map` altogether by using a very java-style `while`. – Paolo Falabella Apr 07 '12 at 13:47
  • I tried your benchmark above with the map, then substituted in the for-expression, which was 5 - 6 times slower. Don't you find the same? – Luigi Plinge Apr 07 '12 at 15:08
  • @LuigiPlinge sorry I didn't get you had tried my own benchmark (I thought you were saying that for were generally slower). That's strange, I don't find significant differences: all versions take more or less the same time for me (I even tried -optimize, which doesn't do anything for me). I'm on Scala version 2.9.1.final (OpenJDK Server VM, Java 1.6.0_23) on Linux 32 Bit. What are you using? – Paolo Falabella Apr 07 '12 at 17:08
  • I tried it on 2.9.0-1 with Java 1.6.0_22 32-bit and got the 500% difference, but this might be partly a result of memory, as it caused OutOfMemoryError on different machine with 2.9.1 / Java 1.7.0 64-bit. Reducing the size of the List, it was still 50-100% slower with the for-expression on both. I don't know exactly why. – Luigi Plinge Apr 07 '12 at 18:00
  • @LuigiPlinge weird... thanks for letting me know! I'll try on my other computer once I'm home. I had always assumed that a for comprehension like the one I wrote and the corresponding map were not only behaving in the same way functionally, but were literally equivalent at the bitecode level. However this difference in behavior makes me think that I may have always been wrong (or that there's a bug in the scala compiler). Happy Easter! – Paolo Falabella Apr 07 '12 at 20:41