Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic getter method for tuples in Scala which preserves dynamic type?

I am hoping to write a Scala method which takes in a tuple of any size and type along with an index, and returns the element in the tuple at that index. I know how to do everything but preserve the type. I haven't yet figured out a way to make the return value be of the dynamic type of the tuple item.

Here is the function I have so far:

def subscript_get(tup: Product, index:Int): Any={
    return tup.productElement(index)    
}

The usage for example would be:

subscript_get((0,1,2,3),0) --> Int = 0

subscript_get((0,1,"asdf",3),2) --> java.lang.String = asdf

I know that I can cast the result back afterwards to what I am looking for, but this doesn't work for me because I can't always know what type I should cast to.

Is something like this even possible ? Thanks!

like image 753
Peter Avatar asked Apr 30 '13 22:04

Peter


1 Answers

I'm not sure you want a solution that uses macros, but for the record (and since I've written precisely this method before), here's how you can implement this with the macro system in 2.10.

As I note in a comment above, this approach requires index to be an integer literal, and relies on "underspecified but intended" behavior in 2.10. It also raises some tricky questions about documentation.

import scala.language.experimental.macros
import scala.reflect.macros.Context

object ProductIndexer {
  def at[T <: Product](t: T)(index: Int) = macro at_impl[T]
  def at_impl[T <: Product: c.WeakTypeTag](c: Context)
    (t: c.Expr[T])(index: c.Expr[Int]) = {
    import c.universe._

    index.tree match {
      case Literal(Constant(n: Int)) if
        n >= 0 && 
        weakTypeOf[T].members.exists {
          case m: MethodSymbol => m.name.decoded == "_" + (n + 1).toString
          case _ => false
        } => c.Expr[Any](Select(t.tree, newTermName("_" + (n + 1).toString)))
      case Literal(Constant(_: Int)) => c.abort(
        c.enclosingPosition,
        "There is no element at the specified index!"
      )
      case _ => c.abort(
        c.enclosingPosition,
        "You must provide an integer literal!"
      )
    }
  }
}

And then:

scala> import ProductIndexer._
import ProductIndexer._

scala> val triple = (1, 'a, "a")
triple: (Int, Symbol, String) = (1,'a,a)

scala> at(triple)(0)
res0: Int = 1

scala> at(triple)(1)
res1: Symbol = 'a

scala> at(triple)(2)
res2: String = a

All statically typed as expected, and if you give it an index that's out of range (or not a literal), you get a nice compile-time error.

like image 104
Travis Brown Avatar answered Sep 29 '22 08:09

Travis Brown