6

I need to write a function that takes a Tuple of String of any size, call trims on each element and returns a new tuple. I am kind of stuck at this point below and the code is already not type safe. In addition I do not know how to go back to a tuple once I convert it to an Iterator. Is there a more elegant way to solve this problem? The solutions needs to work on Scala 2.9.2

  def trim(input:Product)={
    input.productIterator.asInstanceOf[Iterator[String]].map(_.trim)
  }
Mansur Ashraf
  • 1,337
  • 3
  • 9
  • 12
  • 4
    use shapeless or even better: a list – kiritsuku May 09 '13 at 21:55
  • This post might help with getting the iterator back to a tuple: http://stackoverflow.com/questions/11305290/is-there-way-to-create-tuple-from-listwithout-codegeneration – cmbaxter May 09 '13 at 22:12
  • 1
    You can't have tuples of "any size", only up to 22. For arbitrary numbers of items of identical type, use almost any other collection: List, Vector, Array, etc.. – Rex Kerr May 09 '13 at 22:24
  • A tuple is *not* a collection. The very idea of "doing something to every element" of a tuple doesn't make sense. That's why, no matter how good shapeless is, you will always end up with quite a lot of ugly boilerplate. You are simply using the wrong data structure. – Jörg W Mittag May 10 '13 at 10:10
  • @JörgWMittag: Agreed, in most cases, and possibly in the OP's, but _sometimes_ you actually do want a (potentially heterogeneous) collection where the length and all the types are known at compile time, and _sometimes_ a tuple is a perfectly respectable way of modeling such a collection, and _sometimes_ you want to write code that works generically across similar collections of this sort. – Travis Brown May 10 '13 at 11:40
  • 2
    Picking Tuple as a data structure was not by choice. I am using Scalding which basically model everything as Tuple and in different scalding jobs I get tuple of various size that I need to operate on and a generic function that can operate on a tuple of any size is a blessing – Mansur Ashraf May 10 '13 at 19:07

1 Answers1

16

If you're willing to go with a solution that uses Shapeless, this is pretty straightforward (in Shapeless terms, at least):

import shapeless._

object trimmer extends (String -> String)(_.trim)

def trim[T <: Product, L <: HList](t: T)(implicit
  hlister: HListerAux[T, L],
  toList: ToList[L, String],
  mapper: MapperAux[trimmer.type, L, L],
  tupler: TuplerAux[L, T]
) = hlister(t).map(trimmer).tupled

And then:

scala> trim((" a ", "b ", " c"))
res0: (String, String, String) = (a,b,c)

scala> trim((" a ", "b ", " c", "d"))
res1: (String, String, String, String) = (a,b,c,d)

Everything's statically typed appropriately, and if you try to feed it a tuple with any non-String elements, you'll get an error at compile time.

Without a library like Shapeless—which effectively packages all the boilerplate up for you—you're stuck with two options: give up type safety, or write a special case for every tuple size you care about (up to the maximum of 22).

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • That's a lot of implicit args. – pedrofurla May 10 '13 at 01:44
  • 3
    @pedrofurla: Yep. It's also a lot of compile-time evidence that your types are correct. – Travis Brown May 10 '13 at 01:55
  • What would be the runtime performance characteristic of this function? I need to use it inside a map reduce job over a very large dataset. – Mansur Ashraf May 10 '13 at 03:47
  • 2
    You'd need to benchmark your code to find out, but my experience with Shapeless is that performance is usually better than I expect. I think this is because most of the work is done at compile time - there's no run-time reflection or similar. – James_pic May 10 '13 at 07:47
  • @MansoorAshraf: You can see an example of a (very off-the-cuff) benchmark [here](http://stackoverflow.com/a/11439044/334519). My experience is the same as James's—yes, you pay for the genericity / type safety, but not as much as you might expect, because the heavy lifting happens at compile time. – Travis Brown May 10 '13 at 11:27