Skip to content
This repository
Miles Sabin
file 133 lines (113 sloc) 4.552 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
/*
* Copyright (c) 2011 Miles Sabin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package shapeless.examples

import scala.language.existentials

object ReflectionUtils {
  import scala.reflect.api.{ Mirror => APIMirror, TreeCreator, Universe }
  import scala.reflect.runtime.currentMirror
  import scala.reflect.runtime.universe._
  import scala.tools.reflect.Eval

  val byteTypeId = Select(Ident(TermName("scala")), TypeName("Byte"))
  val charTypeId = Select(Ident(TermName("scala")), TypeName("Char"))
  val shortTypeId = Select(Ident(TermName("scala")), TypeName("Short"))
  val intTypeId = Select(Ident(TermName("scala")), TypeName("Int"))
  val longTypeId = Select(Ident(TermName("scala")), TypeName("Long"))
  val floatTypeId = Select(Ident(TermName("scala")), TypeName("Float"))
  val doubleTypeId = Select(Ident(TermName("scala")), TypeName("Double"))
  val booleanTypeId = Select(Ident(TermName("scala")), TypeName("Boolean"))

  def anyToTypeId(a : Any) = a match {
    case _ : Byte => byteTypeId
    case _ : Char => charTypeId
    case _ : Short => shortTypeId
    case _ : Int => intTypeId
    case _ : Long => longTypeId
    case _ : Float => floatTypeId
    case _ : Double => doubleTypeId
    case _ : Boolean => booleanTypeId
    case other =>
      other.getClass.getName.split('.') match {
        case Array(unpackaged) => Ident(TypeName(unpackaged))
        case Array(root, suffix @ _*) =>
          val (pathSuffix, typeName) = (suffix.init, suffix.last)
          Select(
            pathSuffix.map(TermName.apply).foldLeft(Ident(TermName(root)) : Tree)(Select.apply),
            TypeName(typeName)
          )
      }
  }

  def mkExpr[T : TypeTag](mirror: Mirror)(tree : Tree) : mirror.universe.Expr[T] =
    mirror.universe.Expr[T](mirror, new TreeCreator {
      def apply[U <: Universe with Singleton](m : APIMirror[U]) : U#Tree =
        if (m eq mirror) tree.asInstanceOf[U#Tree]
        else throw new IllegalArgumentException(s"Expr defined in $mirror cannot be migrated to other mirrors.")
    })
}

object StagedTypeClassExample extends App {
  import scala.reflect.runtime.universe._
  import scala.reflect.runtime.currentMirror
  import scala.tools.reflect.Eval
  import ReflectionUtils._

  trait TupleConsumer[A, B] {
    def apply(t : (A, B)) : String
  }

  object TupleConsumer {
    implicit def intString = new TupleConsumer[Int, String] {
      def apply(t : (Int, String)) = t._1+t._2
    }
    implicit def booleanDouble = new TupleConsumer[Boolean, Double] {
      def apply(t : (Boolean, Double)) = (if(t._1) "+" else "-")+t._2
    }
  }

  def consumeTuple[A, B](t : (A, B))(implicit tc : TupleConsumer[A, B]) : String = tc(t)

  def stagedConsumeTuple(rawTuple : (Any, Any)) : String = {
    val tpe1 = anyToTypeId(rawTuple._1)
    val tpe2 = anyToTypeId(rawTuple._2)

    val tupleTree =
      TypeApply(
        Select(
          Ident(TermName("rawTuple")),
          TermName("asInstanceOf")),
        List(
          AppliedTypeTree(
            Select(
              Ident(TermName("scala")),
              TypeName("Tuple2")),
            List(tpe1, tpe2))))

    val consumeTree =
      Apply(
        Select(
          Select(
            Select(
              Ident(TermName("shapeless")),
              TermName("examples")),
            TermName("StagedTypeClassExample")),
          TermName("consumeTuple")),
        List(tupleTree))

    val consumeExpr = mkExpr[String](currentMirror)(consumeTree)

    val fnExpr = reify { (rawTuple : (Any, Any)) => consumeExpr.splice }

    val fn = fnExpr.eval
    fn(rawTuple)
  }

  val t1 : (Any, Any) = (23, "foo") // Specific element types erased
  val t2 : (Any, Any) = (true, 2.0) // Specific element types erased

  // Type class instances selected on static type at runtime!

  val c1 = stagedConsumeTuple(t1) // Uses intString instance
  assert(c1 == "23foo")
  println(c1)

  val c2 = stagedConsumeTuple(t2) // Uses booleanDouble instance
  assert(c2 == "+2.0")
  println(c2)
}
Something went wrong with that request. Please try again.