6

Given something like:

class A {
  def f(x: X) = ...
  def g(y: Y, z: Z) = ...
  ...
}

How to (automatically) extract the function(s):

object A {
  val f' = (a: A, x: X) => a.f(x)  // do this automagically for any arbitrary f
  val g' = (a: A, y: Y, z: Z) => a.g(y, z) // and deal with arity > 1 too
  ...
}

With this exact type signature (first the object, then the parameter list). Let me state the problem very clearly:

"Given a method f(x1: X1, ..., xn: Xn) defined in the context of a class A, how to automatically extract a function f' that (i) receives an instance a of type A, and (ii) a parameter list that corresponds 1:1 to the parameter list of f, viz. x1: X, ... xn: Xn, which implementation is exactly a.f(x1: X1, ..., xn: Xn)"

Or even:

Capturing the concept of extensionality of lambda-calculus, such that you automatically extract an λx.(f x) from f whenever x does not appear free in f.

This could first be tackled by finding a way access the identifiers f, g, ... without having a specific a: A (one would have a specific A later, of course). We could simply write f' or g' by hand, but let's indulge DRYness.


P.S. Maybe this isn't possible without runtime reflection (albeit it may be possible with Scala 2.10+ macros), since I can't seem to find a way to refer to the identifiers of f or g without a specific instance (a: A) beforehand. But it would be something like the following, without having to resort to strings:

A.getMethod("f"): Function2[A, X, ...]

I also realize that practical uses of the question may help the participants to suggest alternatives, but I'm discussing this in an abstract sense. I'm not trying to solve other problem which I've reduced to this one. I'm trying to know if this one is possible :-) Here's a very nice article to actually understand the motivation behind this question, with rants over Eta-expansions on Scala.

Hugo Sereno Ferreira
  • 8,600
  • 7
  • 46
  • 92
  • Using reflection for such purposes really is not a good idea. It will inevitably turn out to be an overcomplication, it will reduce the performance and it will destroy the whole point of using a static language, since all your method resolutions and invocations will be done in runtime. You should really consider different approaches to your problem, like, for instance, using implicits. Probably extending your question with appropriate info might help others help you with that. – Nikita Volkov Oct 07 '12 at 11:53
  • @NikitaVolkov I agree with you. Reflection is bad. But if it is solved at compile time, then there's no reason for reduced performance, nor lost of type-safeness. – Hugo Sereno Ferreira Oct 07 '12 at 12:02
  • Reflection can't be solved at compile time, since the whole point of it is in introspecting your program at runtime. Since 2.10 there is a macros feature in Scala, but while being able to solve your problem at compile time it definitely seems to be an overkill. I'm not an expert in it, so I can't help you with that. And btw, in the current state your question is very broad and is likely to be closed. It's hard to get what you're actually asking here. – Nikita Volkov Oct 07 '12 at 12:17
  • 1
    @NikitaVolkov Reflection is made of Introspection (to meta) and Reification (from meta). It is arguable if reflection is runtime-only (although wikipedia says so), so for the sake of the argument, let's call it compile-time meta-programming. – Hugo Sereno Ferreira Oct 07 '12 at 13:38
  • @NikitaVolkov as for the scope of the question, I completely disagree. I've tried (just look at the edits) to be as precise as possible in the problem. I even give two examples. My opinion is that it *is* a tricky question that will possibly involve macros, or intimate knowledge of the Scala syntax or Scalaz magic. Please, don't try to dumb it down just because it doesn't fit the usual pattern of "I want X to accomplish Y, but if you can give me a Z that does the trick, I will be satisfied". – Hugo Sereno Ferreira Oct 07 '12 at 14:03
  • @HugoSFerreira - regarding "let's call it compile-time meta-programming" I suspect you may be getting reflection and macros confused. Scala's macros are definitely compile-time meta-programming (and are built on top of reflection) but reflection by itself isn't. – Paul Butcher Oct 07 '12 at 14:40
  • 2
    Only to clarify, macros implementation uses compile-time reflection, so that the macro implementers can in fact query the structure of the program being compiled. But there are various limitations to what can be done with macros. – pedrofurla Oct 07 '12 at 16:27
  • 1
    @PaulButcher my observation is that **reflection** doesn't **necessarily** happen at **runtime**. There has been [several](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3403.pdf) [prior](http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/oopsla98/proc/berger.pdf) [literature](http://www.artima.com/weblogs/viewpost.jsp?thread=289749) talking about compile-time reflection, so I'm a little surprised that **you** guys are surprised :-) – Hugo Sereno Ferreira Oct 07 '12 at 16:54
  • I'm not surprised at all blame java... Compile time and runtime meta programming seems much more clean – AndreasScheinert Oct 08 '12 at 10:12

3 Answers3

2

You certainly could do this at compile time with macros - I do something very like this in the preview release of ScalaMock 3 - mock objects are instances of anonymous classes where each member is implemented by an instance of a mock function. You may well be able to use it as a starting point for what you're trying to do.

Warning: ScalaMock 3 currently only works with Scala 2.10.0-M6. It doesn't work with M7 or the current development version due to breaking changes in the macro API that I haven't (yet!) had a chance to address.

Paul Butcher
  • 10,722
  • 3
  • 40
  • 44
  • Thanks for the feedback! Could you give use some hints? :-) – Hugo Sereno Ferreira Oct 07 '12 at 16:55
  • I'm not sure that I can really say much more that's very helpful I'm afraid. I'm certain that what you're trying to do is possible, but it's not going to be trivial. I suspect that, as I've had to do in ScalaMock, you're probably going to have to construct the tree you want "by hand", and you're going to run into many of the same complications I have related to handling type-parameterised and overloaded methods. The source file to look at is https://github.com/paulbutcher/ScalaMock/blob/develop/core/src/main/scala/org/scalamock/Mock.scala - I suspect that you could use it as a starting point. – Paul Butcher Oct 08 '12 at 11:32
  • You should also be aware that this area is very much in flux at the moment. The code I just pointed you at works against M6, but is comprehensively broken against M7 (and even more broken against the current development version of 2.10). – Paul Butcher Oct 08 '12 at 11:34
0

This feels similar to Lensed project that creates lenses for Scala case classes. I believe it would be possible to modify it to create the methods you describe instead of lenses.

Community
  • 1
  • 1
Petr
  • 62,528
  • 13
  • 153
  • 317
  • 1
    Unfortunately Lensed uses a compiler plugin. As I know from bitter experience with ScalaMock 2, there are significant limitations with compiler plugins (which is why ScalaMock 3 is moving to using macros) – Paul Butcher Oct 08 '12 at 11:24
-2

I know it's not a particularly beautiful solution, but you if you can't use macros, you could generate source code for a companion object you need and compile it in a separate build step.

def generateCompanionObject(clazz: Class[_]) = {
     val className = clazz.getName.split("\\.").last
     val firstMethod = clazz.getMethods.head//for simplicity
     val methodName = firstMethod.getName
     val parametersClasses = firstMethod.getParameterTypes.map(_.getName).toSeq

     val objectDefinition = "object " + className + " {\n" +
         generateMethodDefinition(className, methodName, parametersClasses) +
         "\n}"

     objectDefinition
 }

 def generateMethodDefinition(className: String, methodName: String, parameterClasses: Seq[String]) = {
     val newMethodName: String = "   def invoke" + methodName.capitalize + "On"

     val parameterList: String = "(o:" + className + ", " + parameterClasses.zipWithIndex.map {
         case (argClassName, index) => "arg" + index + ": " + argClassName
     }.mkString(", ") + ")"

     val generateOldMethodCall: String = "o." + methodName + parameterClasses.zipWithIndex.map {
         case (argClassName, index) => "arg" + index
     }.mkString("(", ",", ")")

     newMethodName + parameterList + " = " + generateOldMethodCall
 }

for class

class A {
   def foo(x: String, y: String) = x + y
}

it will generate

object A {
   def invokeFooOn(o:A, arg0: java.lang.String, arg1: java.lang.String) = o.foo(arg0,arg1)
}
mmmbell
  • 438
  • 2
  • 13