6

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!

Peter
  • 61
  • 3
  • It's possible to do this with macros in 2.10, but only for literal `index` arguments (and only because of [this](http://stackoverflow.com/q/13669974/334519) weird bit of "underspecified but intended" behavior). – Travis Brown Apr 30 '13 at 23:39
  • Macros notwithstanding, it's basically nonsense to talk about making dynamic type information available statically. – Randall Schulz May 01 '13 at 04:46
  • 1
    @TravisBrown has given you a good answer for literal-using call-sites. Can you give some examples of what you have in mind for non-literal-using call-sites for `subscript_get`? Specifically, are you using `Product` in the signature above because you really want the type to be deferred until runtime, or are you using it because you want to abstract over all possible product types at compile time? – Miles Sabin May 01 '13 at 09:26

2 Answers2

11

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.

Community
  • 1
  • 1
Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • This is really nice. Too bad it requires literals. Couldn't we use implicit evidences about the value of the index? :P – gzm0 May 01 '13 at 00:22
0

You cannot do that. If you use Product, the (compile-time) type of the values in the tuples is lost. Further, a method cannot adapt its return type based on an value you pass in (not entirely true, see dependent method types, but true for an Int).

If you do not know what type to cast to, you could use pattern matching:

subscript_get(..., 1) match {
  case v: Int    => // do something with Int
  case v: String => // do something with String
  // snip
  case _ => sys.error("don't know how to handle this")
}
Community
  • 1
  • 1
gzm0
  • 14,752
  • 1
  • 36
  • 64