Friday, December 18, 2009

Tuples are (not?) Collections

Tuples are quite handy but a potential annoyance is that at a glance they seem list-like but appearances can be deceiving. The normal collection methods are not supported by Tuples. For example suppose you like the simple syntax: (1,2,3) better than List(1,2,3). With the Tuple you cannot do the maps, filters, etc... (with good reason) but there is a use-case for being able to convert a Tuple to a Traversable.

Word of warning. Unlike collections Tuples are very often not homogeneous. IE you do not have Tuple2[Int] you have Tuple2[A,B]. So the best you can do is to map a tuple to an Iterator[Any].

Note: this was done with Scala 2.8 so results may be slightly different with 2.7. But I believe the syntax is valid.
  1. // the productIterator method gives access to an iterator over the elements
  2. scala> (1,2,3).productIterator.map {_.toString} mkString (",")
  3. res1: String = 1,2,3
  4. scala> (1,2,3).productIterator foreach {println _}            
  5. 1
  6. 2
  7. 3
  8. // if you know the tuple is heterogeneous then you can use partial functions
  9. // for casting the elements to a particular type.  
  10. scala> (1,2,3).productIterator map {case i:Int => i + 2} foreach {println _}
  11. 3
  12. 4
  13. 5
  14. // To get a full Traversable out of the deal you use one of the many
  15. // to* methods to convert to Seq, List, Array, etc...
  16. scala> (1,2,3).productIterator.toList map {case i:Int => i + 2}      
  17. res15: List[Int] = List(3, 4, 5)
  18. // and if you want you can use an implicit to clean up the syntax a bit
  19. // Problem with this is you need an implicit for each Tuple length
  20. scala> implicit def tupleToTraversable[T](t:(T,T,T)) = t.productIterator.toList map { case e:T => e}
  21. warning: there were unchecked warnings; re-run with -unchecked for details
  22. tupleToTraversable: [T](t: (T, T, T))List[T]
  23. scala> (1,2,3) foreach {println _}
  24. 1
  25. 2
  26. 3
  27. /* 
  28. EDIT:  Dan pointed out that the methods I am using are inherited from the
  29. Product super class of Tuple.  So you can do something similar as follows.
  30. Note:  I am using the same name as the previous method so that they don't interfer 
  31. with one another
  32. */
  33. scala> implicit def tupleToTraversable[T](t:Product) = t.productIterator.toList map { case e:T => e} 
  34. warning: there were unchecked warnings; re-run with -unchecked for details
  35. tupleToTraversable: [T](t: Product)List[T]
  36. scala> (1,2,3) foreach {println _}
  37. 1
  38. 2
  39. 3
  40. // this is interesting... it does cast to int unless required
  41. scala> (1,2,'t') foreach {println _}
  42. 1
  43. 2
  44. t
  45. // lets verify we are getting correct conversion
  46. scala> def inc(l:List[Int]) = l map {_ + 1} 
  47. inc: (l: List[Int])List[Int]
  48. scala> inc ( (1,2,3))                              
  49. res4: List[Int] = List(2, 3, 4)
  50. // yep this I expected
  51. scala> inc ( (1,2,'t'))
  52. java.lang.ClassCastException: java.lang.Character cannot be cast to java.lang.Integer
  53. at scala.runtime.BoxesRunTime.unboxToInt(Unknown Source)
  54. at $anonfun$inc$1.apply(< console>:7)
  55. at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:238)
  56. at .< clinit>(< console>)
  57. scala> def inc(l:List[Int]) = l foreach {println _}
  58. inc: (l: List[Int])Unit
  59. scala> def p(l:List[Int]) = l foreach {println _}  
  60. p: (l: List[Int])Unit
  61. scala> p ( (1,2,'t'))                              
  62. 1
  63. 2
  64. t

5 comments:

  1. Couldn't you get something similar for all tuples by using Product as the parameter type instead of the individual tuple types? All of the methods that you are using on a tuple are inherited from the Product type.

    ReplyDelete
  2. The truly sad thing is that I have even done that in one of my project but forgot when doing this post. I will update this post as soon as I am back at the computer

    thanks

    ReplyDelete
  3. I vote for an entire post devoted to Product since... I don't know what that is :-)

    ReplyDelete
  4. @Dan On second thought there is a small difference from what I was doing here and just using Product. If i go the product route I can't statically verify that all elements of the tuple are of the same type.

    I am updating the post to reflect the difference.

    ReplyDelete
  5. @HandyMan: Topic added for the future.

    ReplyDelete