constant, compiler-checked expressions for fields and methods?

103 views
Skip to first unread message

Gerald Loeffler

unread,
Sep 21, 2012, 1:34:31 PM9/21/12
to scala-...@googlegroups.com

hi,

i couldn't find any Scala discussions about this (in Java-land it has been discussed aplenty, e.g. here and here), so must ask here if this has been discussed before, and if so, what the consensus was, if any:

Most Java frameworks refer to classes through java.lang.Class objects, because with the MyClass.class syntax Java directly supports compile-time-constant expressions creating Class objects. Conversely, to refer to fields or methods of a Java class, those same frameworks need to resort to using Strings, because Java does not provide compile-time-constant expressions for constructing java.lang.reflect.Field and java.lang.reflect.Method objects. For instance, in JPA/Java

@Entity
@EntityListeners(MyListener.class)
class Team {
  @OneToMany(mappedBy="team")
  Set<Player> players;
}

@Entity
class Player {
  @ManyToOne
  Team team;
}  

Thus the reference to the Class object for class MyListener is checked by the compiler, while the reference to the Field object for Player::team must be simulated by the compile-time-unchecked String "team". Similar example where methods are identified by their String name abound.

Has there been a proposal for Scala to directly support syntax for constructing compile-time-constant java.lang.reflect.Field and java.lang.reflect.Method objects along the lines it currently does for java.lang.Class objects? Maybe like so:

@Entity
@EntityListeners(classOf[MyListener])
class Team {
  @OneToMany(mappedBy=fieldOf[Player#team].getName)
  Set<Player> players
}

and similar for methods? Thank you very much for your feedback.

  cheers
  gerald

Simon Ochsenreither

unread,
Sep 21, 2012, 2:00:31 PM9/21/12
to scala-...@googlegroups.com
Hi Gerald,

this is coming up from time to time on the mailing list ... let me search for it ... mhhh, can't come up with the right search phrases, but it's there, I know it... :-)

As far as I remember, there are a few issues related to overloading which are hard to solve currently.

My personal guess is that there might be an increase of people playing with these ideas again when we understand what can be done with MethodHandles (only slightly related to the issues) and type providers.

I'll try to find the earlier discussion again ...

Bye,

Simon

Paul Phillips

unread,
Sep 21, 2012, 10:27:06 PM9/21/12
to Gerald Loeffler, scala-...@googlegroups.com
On Fri, Sep 21, 2012 at 10:34 AM, Gerald Loeffler <gerald....@gmail.com> wrote:

Has there been a proposal for Scala to directly support syntax for constructing compile-time-constant java.lang.reflect.Field and java.lang.reflect.Method objects along the lines it currently does for java.lang.Class objects?

I don't remember writing anything down, but I've always wanted to do this.  I have heard it referred to as "static reflection" and I think if we had it we'd wonder how we lived without it.  I guess macros are the most plausible route right now.

Gerald Loeffler

unread,
Sep 22, 2012, 4:01:13 AM9/22/12
to Simon Ochsenreither, scala-...@googlegroups.com
thanks Simon - a pointer to old discussion threads would be much appreciated! - gerald
--
Gerald Loeffler
mailto:gerald....@googlemail.com
http://www.gerald-loeffler.net

Gerald Loeffler

unread,
Sep 22, 2012, 4:07:53 AM9/22/12
to Paul Phillips, scala-...@googlegroups.com
On Saturday, September 22, 2012, Paul Phillips wrote:

I don't remember writing anything down, but I've always wanted to do this.  I have heard it referred to as "static reflection" and I think if we had it we'd wonder how we lived without it.  I guess macros are the most plausible route right now.


How did we ever live without it? Not very well: code like this is routinely littered with countless string-references to field and method names, which can and do break under refactoring...

If macros provide a basis for implementing this, then this feature request would be most timely ;-) Exciting!

Daniel Sobral

unread,
Sep 22, 2012, 3:01:23 PM9/22/12
to Gerald Loeffler, Paul Phillips, scala-...@googlegroups.com
Well, the reflection for it would be something like this:

def getFieldSymbol[T: TypeTag](fieldName: String): Option[TermSymbol]
= typeOf[T].members.find(x => x.name == newTermName(fieldName + " ")
&& x.isTerm && x.asTerm.isVariable).map(_.asTerm)

I expect the macro wouldn't be all that different, aside from
returning a TermSymbol or error. I'm not feeling particularly well
right now, though, so I'll pass on writing it.

--
Daniel C. Sobral

I travel to the future all the time.

Paul Phillips

unread,
Sep 22, 2012, 4:12:15 PM9/22/12
to Gerald Loeffler, scala-...@googlegroups.com


On Sat, Sep 22, 2012 at 1:07 AM, Gerald Loeffler <gerald....@gmail.com> wrote:
If macros provide a basis for implementing this, then this feature request would be most timely ;-) Exciting!

I didn't want to spend the rest of my life figuring out how to pass types between universes, or I would have returned the symbol and type of the method.  However this should be convincing enough that we can have static checking today.


package improving

import scala.reflect.runtime.{ universe => ru }
import scala.language.experimental.macros
import scala.reflect.macros.Context

/** Written against 2.10.0-M7.
 */
object StaticReflect {
  def method[A](name: String): String = macro methodImpl[A]

  def methodImpl[A: c.AbsTypeTag](c: Context)(name: c.Expr[String]): c.Expr[String] = {
    import c.universe._

    val nameName: TermName = name.tree match {
      case Literal(Constant(str: String)) => newTermName(str)
      case _                              => c.error(c.enclosingPosition, s"Method name not constant.") ; return reify("<error>")
    }
    val clazz  = implicitly[c.AbsTypeTag[A]].tpe
    clazz member nameName match {
      case NoSymbol => c.error(c.enclosingPosition, s"No member called $nameName in $clazz.") ; reify("<error>")
      case member   => c.Expr[String](Literal(Constant((member typeSignatureIn clazz).toString)))
    }
  }
}

/***

import improving.StaticReflect._
class A {
  val x = method[List[Int]]("flatMap")
  val y = method[List[Int]]("flatBippy")
}

% m7scalac -cp target/scala-2.10/macrocosm_2.10-0.2-SNAPSHOT.jar a.scala
a.scala:5: error: No member called flatBippy in List[Int].
  val y = method[List[Int]]("flatBippy")
                           ^
one error found

****/

Paul Phillips

unread,
Sep 22, 2012, 4:13:11 PM9/22/12
to Gerald Loeffler, scala-...@googlegroups.com
Oh, I left out the part where you get something.

scala> import improving.StaticReflect._
import improving.StaticReflect._

scala> method[List[Int]]("flatMap")
res0: String = [B, That](f: Int => scala.collection.GenTraversableOnce[B])(implicit bf: scala.collection.generic.CanBuildFrom[List[Int],B,That])That

Eugene Burmako

unread,
Sep 22, 2012, 4:42:03 PM9/22/12
to Paul Phillips, Gerald Loeffler, scala-...@googlegroups.com
What universes do want to pass types between?

Paul Phillips

unread,
Sep 22, 2012, 5:37:46 PM9/22/12
to Eugene Burmako, Gerald Loeffler, scala-...@googlegroups.com


On Sat, Sep 22, 2012 at 1:42 PM, Eugene Burmako <eugene....@epfl.ch> wrote:
What universes do want to pass types between?

Well, the macro gives me some kind of Symbol and Type, and I want to give them to the unknown person calling the method.  So I suppose that's "macro universe" to "runtime universe".  This seems like the most common thing to want to do, so I imagine it's easy.


Paul Phillips

unread,
Sep 22, 2012, 5:38:31 PM9/22/12
to Eugene Burmako, Gerald Loeffler, scala-...@googlegroups.com
You can see this method signature:

def methodImpl[A: c.AbsTypeTag](c: Context)(name: c.Expr[String]):
c.Expr[String]

I want it to be this:

def methodImpl[A: c.AbsTypeTag](c: Context)(name: c.Expr[String]):
c.Expr[(ru.Symbol, ru.Type)]

Eugene Burmako

unread,
Sep 22, 2012, 6:16:08 PM9/22/12
to Paul Phillips, Gerald Loeffler, scala-...@googlegroups.com
Sure. That's what reify does.

You can call c.reifyType, which takes a universe tree (you can use c.runtimeUniverse) + a mirror tree (e.g. Select(c.runtimeUniverse, newTermName("rootMirror")) + a type you want to reify. This will produce a tree, which at runtime will evaluate to a TypeTag, instantiated in the provided universe and mirror.

As to symbol, there's no direct way of doing that, but you could wrap it in a tree, say, in an Ident, call c.reifyTree, and then unwrap the result.

Paul Phillips

unread,
Sep 22, 2012, 6:54:59 PM9/22/12
to Eugene Burmako, Gerald Loeffler, scala-...@googlegroups.com


On Sat, Sep 22, 2012 at 3:16 PM, Eugene Burmako <eugene....@epfl.ch> wrote:
> You can call c.reifyType, which takes a universe tree (you can use
> c.runtimeUniverse) + a mirror tree (e.g. Select(c.runtimeUniverse,
> newTermName("rootMirror")) + a type you want to reify. This will produce a
> tree, which at runtime will evaluate to a TypeTag, instantiated in the
> provided universe and mirror.

I wouldn't have come up with that real quickly.  Here's what I managed.  There's some kind of weird category-error taking place....

object StaticReflect {
  def method[A](name: String): ru.Type = macro methodImpl[A]

  def methodImpl[A: c.AbsTypeTag](c: Context)(name: c.Expr[String]): c.Expr[ru.Type] = {

    import c.universe._

    val nameName: TermName = name.tree match {
      case Literal(Constant(str: String)) => newTermName(str)
      case _                              => c.error(c.enclosingPosition, s"Method name not constant.") ; return reify(ru.NoType)

    }
    val clazz  = implicitly[c.AbsTypeTag[A]].tpe

    clazz member nameName match {
      case NoSymbol => c.error(c.enclosingPosition, s"No member called $nameName in $clazz.") ; reify(ru.NoType)
      case member   =>
        val mtpe  = member typeSignatureIn clazz
        val mtag  = c.reifyType(c.runtimeUniverse, Select(c.runtimeUniverse, newTermName("rootMirror")), mtpe)
        val mtree = Select(mtag, newTermName("tpe"))

        c.Expr[ru.Type](mtree)

    }
  }
}

scala> import improving.StaticReflect._
import improving.StaticReflect._

scala> import scala.reflect.runtime.{ universe => ru }
import scala.reflect.runtime.{universe=>ru}

// works for simple cases
scala> val x: ru.Type = method[List[Int]]("distinct")
x: reflect.runtime.universe.Type = => List[Int]

// but not for less simple cases
scala> val x: ru.Type = method[List[Int]]("map")
<console>:11: error: type arguments [(f: Int => B)(implicit bf: scala.collection.generic.CanBuildFrom[List[Int],B,That])That] do not conform to method apply's type parameter bounds [T]
       val x: ru.Type = method[List[Int]]("map")
                                         ^


I also tried concrete=true, but predictably:

scala> val x: ru.Type = method[List[Int]]("map")
error: exception during macro expansion: 
scala.reflect.macros.ReificationError: cannot reify TypeTag having unresolved type parameter That
at scala.reflect.reify.Errors$class.CannotReifyTypeTagHavingUnresolvedTypeParameters(Errors.scala:32)
at scala.reflect.reify.Reifier.CannotReifyTypeTagHavingUnresolvedTypeParameters(Reifier.scala:14)
at scala.reflect.reify.States$State.reificationIsConcrete_$eq(States.scala:39)
at scala.reflect.reify.codegen.GenTypes$class.spliceType(GenTypes.scala:100)
at scala.reflect.reify.Reifier.spliceType(Reifier.scala:14)
at scala.reflect.reify.codegen.GenTypes$class.reifyType(GenTypes.scala:27)
at scala.reflect.reify.Reifier.reifyType(Reifier.scala:14)
at scala.reflect.reify.phases.Reify$$anonfun$reify$1.apply(Reify.scala:44)

Paul Phillips

unread,
Sep 22, 2012, 6:59:56 PM9/22/12
to Eugene Burmako, Gerald Loeffler, scala-...@googlegroups.com
Oh I see, it's just List[Int] vs. List[T].  I have trouble working with the parts of the api exposed outside the compiler, because I'm pretty used to the compiler...

Eugene Burmako

unread,
Sep 23, 2012, 6:34:07 AM9/23/12
to scala-debate, Paul Phillips
First of all, a small remark. You can use weakTypeOf[A] instead of implicitly[WeakTypeTag[A]].tpe.

Also the snippet you provided isn't the problem. I copy/pasted it into the test file, compiler and run it - and everything went fine. It looks like the problem is in the macro. I'm looking into it.

On 23 September 2012 01:16, Paul Phillips <pa...@improving.org> wrote:
No, something more exciting is happening.  My Ints are in there, but the fact that it's a polytype is somehow making it go wrong. Why is it talking about "method apply's type parameter bounds [T]" ? Whose method apply is that?

scala> val x: ru.Type = method[List[Int]]("map")
<console>:11: error: type arguments [(f: Int => B)(implicit bf: scala.collection.generic.CanBuildFrom[List[Int],B,That])That] do not conform to method apply's type parameter bounds [T]
       val x: ru.Type = method[List[Int]]("map")
                                         ^

I guess it's somewhere in this thing of beauty:

  $u.AbsTypeTag.apply[(f: Int => B)(implicit bf: scala.collection.generic.CanBuildFrom[List[Int],B,That])That]($m, {
    final class $typecreator1 extends TypeCreator {
      def <init>() = {
        super.<init>();
        ()
      };
      def apply[U >: Nothing <: Universe with Singleton]($m$untyped: MirrorOf[U]): U#Type = {
        val $u: U = $m$untyped.universe;
        val $m: $u.Mirror = $m$untyped.asInstanceOf[$u.Mirror];
        val symdef$B1 = $u.build.newNestedSymbol($u.build.selectTerm($m.staticClass("scala.collection.TraversableLike"), "map"), $u.newTypeName("B"), $u.NoPosition, $u.build.flagsFromBits(8208L), false);
        val symdef$That1 = $u.build.newNestedSymbol($u.build.selectTerm($m.staticClass("scala.collection.TraversableLike"), "map"), $u.newTypeName("That"), $u.NoPosition, $u.build.flagsFromBits(8208L), false);
        val symdef$f1 = $u.build.newNestedSymbol($u.build.selectTerm($m.staticClass("scala.collection.TraversableLike"), "map"), $u.newTermName("f"), $u.NoPosition, $u.build.flagsFromBits(8192L), false);
        val symdef$bf1 = $u.build.newNestedSymbol($u.build.selectTerm($m.staticClass("scala.collection.TraversableLike"), "map"), $u.newTermName("bf"), $u.NoPosition, $u.build.flagsFromBits(8704L), false);
        $u.build.setTypeSignature(symdef$B1, $u.TypeBounds($m.staticClass("scala.Nothing").asType.toTypeConstructor, $m.staticClass("scala.Any").asType.toTypeConstructor));
        $u.build.setTypeSignature(symdef$That1, $u.TypeBounds($m.staticClass("scala.Nothing").asType.toTypeConstructor, $m.staticClass("scala.Any").asType.toTypeConstructor));
        $u.build.setTypeSignature(symdef$f1, $u.TypeRef($u.ThisType($m.staticPackage("scala").asModule.moduleClass), $m.staticClass("scala.Function1"), scala.collection.immutable.List.apply($m.staticClass("scala.Int").asType.toTypeConstructor, $u.TypeRef($u.NoPrefix, symdef$B1, scala.collection.immutable.List.apply()))));
        $u.build.setTypeSignature(symdef$bf1, $u.TypeRef($u.ThisType($m.staticPackage("scala.collection.generic").asModule.moduleClass), $m.staticClass("scala.collection.generic.CanBuildFrom"), scala.collection.immutable.List.apply($u.TypeRef($u.ThisType($m.staticPackage("scala.collection.immutable").asModule.moduleClass), $m.staticClass("scala.collection.immutable.List"), scala.collection.immutable.List.apply($m.staticClass("scala.Int").asType.toTypeConstructor)), $u.TypeRef($u.NoPrefix, symdef$B1, scala.collection.immutable.List.apply()), $u.TypeRef($u.NoPrefix, symdef$That1, scala.collection.immutable.List.apply()))));
        $u.PolyType(scala.collection.immutable.List.apply(symdef$B1, symdef$That1), $u.MethodType(scala.collection.immutable.List.apply(symdef$f1), $u.MethodType(scala.collection.immutable.List.apply(symdef$bf1), $u.TypeRef($u.NoPrefix, symdef$That1, scala.collection.immutable.List.apply()))))
      }
    };
    new $typecreator1()
  })

Eugene Burmako

unread,
Sep 23, 2012, 6:36:27 AM9/23/12
to scala-debate
Oh yep, also instead of "c.error(c.enclosingPosition, s"Method name
not constant.") ; return reify(ru.NoType)" you can use c.abort(...)
without any return. c.abort = c.error + terminate a macro immediately

Eugene Burmako

unread,
Sep 23, 2012, 6:42:17 AM9/23/12
to scala-debate
Okay I found out the problem. It becomes apparent once you dump macro
expansions with -Ymacro-debug-verbose (-Ymacro-debug-lite would work
as well). Here's a fragment of a snippet generated by reifyType:

$u.WeakTypeTag.apply[(f: Int => B)(implicit bf:
scala.collection.generic.CanBuildFrom[List[Int],B,That])That]($m, {
final class $typecreator2 extends TypeCreator {

On Sep 23, 12:34 pm, Eugene Burmako <eugene.burm...@epfl.ch> wrote:

Naftoli Gugenheim

unread,
Sep 24, 2012, 7:49:48 PM9/24/12
to Paul Phillips, Gerald Loeffler, scala-...@googlegroups.com
Why take a String, why not take a =>A, and require it to refer to a method or field? Also, maybe a 'Symbol would be more idiomatic than a String, no?

Naftoli Gugenheim

unread,
Sep 24, 2012, 7:50:44 PM9/24/12
to Gerald Loeffler, scala-...@googlegroups.com
In Scala it's somewhat less necessary than in Java, since you can pass a =>A (for read-only), or a (=>A, A=>Unit) for read-write.
Reply all
Reply to author
Forward
0 new messages