1) I agree with everyone else, please don't call it invokeDynamic. I've
been hearing about invokeDynamic daily for over a year or two now and
this is not what I've been hearing about. I think I would prefer "def
paulpIsStupid" to def invokeDynamic. (Actually I know I would, that
name is awesome.)
2) Taking only Any args buries a vital distinction. There is no way to
tell whether primitives or boxed types were given as arguments. In real
life there are a couple ways to work around this, but in its present
form Dynamic takes all those ways completely out of the game. It is a
serious issue because they are different types to the jvm and reflection
does not offer any wiggle room. It really complicates matters quite a
lot if every time you're given an argument you have to splatter 2^n
pieces of "I wonder if this was boxed" logic around if you don't like
failure.
...which leads to...
3) I we can do far better than some Any-goo. First let's agree that
dynamic languages are slow and nobody will notice a couple light
abstractions. ("It is so agreed.") We can solve 2) and throw in
language-agnostic named arguments and other frostings if, rather than
taking Any*s, we define the method around some simple new Arg container
class. Populating it via implicits lets us hang onto the knowledge of
what is primitive.
abstract class Arg {
type ArgType
def isPrimitive: Boolean
val value: ArgType
def name: String = ""
}
abstract class Primitive extends Arg {
def isPrimitive = true
}
abstract class Reference extends Arg {
def isPrimitive = false
}
// Any isn't so bad as a return type because of covariant override
def invokeSymbolic(name: String)(args: Arg[_]*): Any
implicit def anyval2arg[T <: AnyVal](x: T) = new Primitive { val value = x }
implicit def anyref2arg[T <: AnyRef](x: T) = new Reference { val value = x }
--
This is a sample implementation only. However I know this approach to
reflection works, since I've implemented it at least a couple times. Oh
yeah, I should have started with that. Here, this:
https://github.com/paulp/scala-improving/raw/master/src/main/scala/reflect/AnyExt.scala
Oh, I have a whole prefab comment on this subject. I really need to
stop doing the same work repeatedly.
/** In order to encapsulate anything to do with reflection, we must
* overcome an issue with the boxing of primitives. If we declare a
* method which takes arguments of type Any, by the time the
* method parameters can be examined, the primitives have already
been boxed.
* The reflective call will then fail because classOf[java.lang.Integer]
* is not the same thing as classOf[scala.Int].
*
* Any useful workaround will require examining the arguments before
* the method is called. The approach here is to define different
implicits
* for AnyVal and AnyRef which preserves their original identity as it
* transforms them. We are left only with "Any" as a problem.
*/
4) I don't quite understand the thinking behind the typed method.
/** Returns the underlying value typed as an instance of type T
* @param T The target type
*/
def typed[T]: T
What implementation is this going to have besides the obvious one?
There's not even a manifest to get tricky with. Is this only to offer
more aesthetic casts? If we're coming around to offering more aesthetic
casts then I will be bummed if they are exclusively for this zone.
Those of us with both feet firmly in static type land still have to do
enormous amounts of casting. Between Any and AnyRef for starters. But
not for enders.
But beyond that, the method seems to assume a lot about what I'm doing
with the Dynamic trait. Of the first half dozen things I thought about
doing with it, none really involved an "underlying value" which I want
to offer up in this way. Especially, none involved a value I wanted to
return with a blind cast. I feel like I must be missing something major
on this one because I'm not seeing it.
Here is some code vaguely relevant to earlier points in case anyone has
reached this far distant shore.
class ReflectoWrapper[T <: AnyRef](target: T) extends Dynamic {
def invokeDynamic(name: String)(args: Any*): Any = {
val anyrefs = args map (_.asInstanceOf[AnyRef])
val clazzes = anyrefs map (_.getClass)
val method = target.getClass.getMethod(name, clazzes: _*)
method.invoke(target, anyrefs: _*)
}
def typed[U] : U = target.asInstanceOf[U]
}
class Tricky {
def f(x: Int): String = "primitive"
def f(x: java.lang.Integer): String = "boxed"
}
object Test {
implicit def anyoneToReflective[T <: AnyRef](target: T):
ReflectoWrapper[T] =
new ReflectoWrapper[T](target)
def main(args: Array[String]): Unit = {
val t: AnyRef = new Tricky
// Pretty relieved this doesn't work.
// println(t.f(5))
// These are ok.
println((t: Dynamic).f(5))
println(t.typed[Tricky].f(5))
}
}
// Output:
//
// boxed
// primitive
I'm sure I won't have to insist; sometimes there's only one right name
for a function.
A lucky message reminded me of these forgotten classes, which now that I
have unforgotten I think should not be ignored in this matter.
http://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/compiler/scala/tools/reflect/Mock.scala
http://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/compiler/scala/tools/reflect/Invoked.scala
That second one, Invoked, is a wrapper defining a method call. So the
overlap here is obvious. Mock and Invoked are accomplishing something
you can't do any other way (short of bytecode generation) with or
without Dynamic: implement an interface which is not known at compile
time. But in tandem with Dynamic the possibilities multiply.
Not having heard anything on my other Dynamic ideas I don't know if I'm
just digging my way to china on a boat that will never get there (and
I'm really not trying to complicate anything: to save us from
near-future complications, maybe, and practice my metaphor swirling) but
I had one more idea which I must air.
Here's java.util.Map.
trait Map[K, V] {
def size(): Int
def isEmpty(): Boolean
def containsKey(key: Object): Boolean
def containsValue(value: Object): Boolean
def get(key: Object): V
def put(key: K, value: V): V
def remove(key: Object): V
def putAll(m: Map[_ <: K, _ <: V]): Unit
def clear(): Unit
def keySet(): Set[K]
def values(): Collection[V]
def entrySet(): Set[Map.Entry[K, V]]
}
I write this:
class Mappy extends java.util.Map[String, Int] with Dynamic {
def applyDynamic(name: String)(args: Any*) = /* not the point here */
def typed[T] = sys.error("begone")
def size(): Int = 52
def get(key: Object): V = 5
/* too tired to continue */
}
Compiler says: non-abstract class, extends Dynamic, missing methods. I
hear and obey: supply all the missing implementations.
def isEmpty(): Boolean =
applyDynamic("isEmpty")().asInstanceOf[Boolean]
def containsKey(key: Object): Boolean =
applyDynamic("containsKey")(key).asInstanceOf[Boolean]
// etc
Fun times would ABOUND.
Note: this need not be Dynamic, it could be separated into
DynamicInterface or something.
On 2/3/11 2:44 PM, Paul Phillips wrote:
On 2/2/11 11:41 PM, Paul Phillips wrote:A lucky message reminded me of these forgotten classes, which now that I
have unforgotten I think should not be ignored in this matter.
Not having heard anything on my other Dynamic ideas I don't know if I'm just digging my way to china on a boat
that will never get there (and I'm really not trying to complicate anything: to save us from near-future complications, maybe, and practice my metaphor swirling) but I had one more idea which I must air.
Here's java.util.Map.
trait Map[K, V] {
def size(): Int
def isEmpty(): Boolean
def containsKey(key: Object): Boolean
I write this:
class Mappy extends java.util.Map[String, Int] with Dynamic {
def applyDynamic(name: String)(args: Any*) = /* not the point here */
def typed[T] = sys.error("begone")
def size(): Int = 52
def get(key: Object): V = 5
/* too tired to continue */
}
Compiler says: non-abstract class, extends Dynamic, missing methods. I hear and obey: supply all the missing implementations.
def isEmpty(): Boolean =
applyDynamic("isEmpty")().asInstanceOf[Boolean]
def containsKey(key: Object): Boolean =
applyDynamic("containsKey")(key).asInstanceOf[Boolean]
// etc
Fun times would ABOUND.
Note: this need not be Dynamic, it could be separated into DynamicInterface or something.
So the idea is that, if `obj` not have a method `name`, but `obj`s
type inherits from Dynamic, then
obj.name(args) is interpreted as obj.applyDynamic("name", args).
We do not need an applyDynamic to be defined in the trait for this.
It's good enough for it to be defined in the actual type of `obj`.
That scheme is completely analogous to the way we handle `apply` and
the mapping of for comprehensions.
The upside is that different implementations of Dynamic can implement
applyDynamic with different signatures, according to their needs. And
the spec becomes even shorter than it is now.
Cheers
-- Martin
--
----------------------------------------------
Martin Odersky
Prof., EPFL and CEO, Scala Solutions
PSED, 1015 Lausanne, Switzerland
I think I have a solution to our deliberations what to put into the
Dynamic trait. Let's remove everything that is controversial and we
are left with - NOTHING AT ALL!
So the idea is that, if `obj` not have a method `name`, but `obj`s
type inherits from Dynamic, then
obj.name(args) is interpreted as obj.applyDynamic("name", args).
We do not need an applyDynamic to be defined in the trait for this.
It's good enough for it to be defined in the actual type of `obj`.
That scheme is completely analogous to the way we handle `apply` and
the mapping of for comprehensions.
The upside is that different implementations of Dynamic can implement
applyDynamic with different signatures, according to their needs. And
the spec becomes even shorter than it is now.
I might want to make Rep[MyDSLType] dynamic, but not Rep[Int].
- Tiark
I was also thinking getting rid of the trait would be nice, but for
different reasons. There's no interface you have to implement to be
used in a for comprehension. Requiring an interface to be implemented
brings on grumpy old grandpa JVM and his inflexible rules, but a
fictional one like NotNull would allow us to cast to it. (There could
also be a real interface which implied the fictional one.)
Sort of similarly, there should/could be a "Comprehensible" interface
but we can't properly capture it with only one method signature per
name. Tangent:
https://lampsvn.epfl.ch/trac/scala/ticket/676
"Create trait for things that are comprehendable"
If the signature isn't captured in the Dynamic trait, implicit params
might let you express that constraint.
trait Rep[T] {
def applyDynamic(name: String, args: Any*)(implicit ev: T <:< MyDslType]
}
-jason
Introduce the marker trait now. Investigate whether Jason's way is
good enough to deal with all application scenarios. Only if we come up
against a roadblock for something that we agree is essential, we could
consider the following further relaxation:
if `obj` not have a method `name`, but `obj`s
type _conforms to_ (was: inherits from) Dynamic, then
obj.name(args) is interpreted as obj.applyDynamic("name", args).
We can do this in an upwards compatible way later if needed.
Cheers
-- Martin
I meant dynamic binding, of course. -- M
Sold. I'm digging the good vibes from this scene. (Am attempting to
speak in the language of one of adriaan's hypothetical dynamic language
programmers.)