Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala implicit with variable argument list

I have library method taking variable argument list and producing data

class Data
def process(elems: String*): Data = new Data

and I want my strings to be implicitly converted to Data

implicit def strToData(ts: String): Data = process(t)

so I can write something like

val data: Data = "one"

but I want tuples of strings to be implicitly converted too. I've added another implicit

implicit def strsToData(ts: String*): Data = process(ts: _*)

it compiles fine, but conversion fails

val data: Data = ("one", "two")
val dat3: Data = ("one", "two", "three")
val dat4: Data = ("one", "two", "three", "four")

with

found   : Seq[java.lang.String]
required: this.Data
val data: Data = Seq("one", "two")

Is there any way to convert tuples implicitly, or a reason why it can be achieved?

Update: Tuples can be of any arity.

like image 565
lambdas Avatar asked Jul 27 '12 10:07

lambdas


2 Answers

  1. ts: String* is not a tuple, but an Iterable. You can't convert iterable to tuple, because tuple is a static type and its arity is resolved at compile time. Emil H answered how you can do an implicit conversion from tuple.
  2. Transparent implicit conversions from one type to another type is a common pitfall and are greatly discouraged. Instead you should apply a "wrapper" approach, or use "Value Classes" since Scala 2.10:

    In Scala 2.9:

    implicit def stringStringTupleExtender (ts : (String, String)) =
      new {
        def data = process(ts)
      }
    

    In Scala 2.10:

    implicit class StringStringTupleExtender (val ts : (String, String)) extends AnyVal {
      def data = process(ts)
    }
    

    Then you'll use it like so:

    val data : Data = ("sdf", "lsdfj").data
    val data1 : Data = "sdf".data // if you do the same for String
    
  3. If you're looking for dynamically resolving any input collection, then, guess what, you should use a collection, not a tuple.

    In Scala 2.9

    implicit def seqExtender (ts : Seq[String]) =
      new {
        def data = process(ts)
      }
    

    Usage:

    val data : Data = Seq("sdf", "lsdkfjsdl", "ldsfjk").data
    
like image 139
Nikita Volkov Avatar answered Sep 20 '22 00:09

Nikita Volkov


While all the warnings in @NikitaVolkov's answer apply in double force, you can write a version that accepts any arity of tuple, as well as any case class:

implicit def prod2data(Product p): process((p.productIterator.toSeq):_*)
like image 41
Ptharien's Flame Avatar answered Sep 19 '22 00:09

Ptharien's Flame