-
Notifications
You must be signed in to change notification settings - Fork 23
Request for comments: Methods as properties on POJOs
I'm adding a new metaobject protocol operation, "dyn:call" that can be used to directly call an object that is, well, callable. One obvious target for this operation are the (currently package-protected) DynamicMethod objects that the BeansLinker internally uses to represent Java methods, both instance and static ones. The advantage of going through the MOP for "dyn:call" instead of just calling raw method handles is that DynamicMethod has two subclasses: SimpleDynamicMethod that wraps a single method handle of a non-overloaded method, and provides type conversion and vararg handling for it, and OverloadedDynamicMethod that wraps a set of overloaded methods, and provides overload resolution, plus the type conversion and vararg handling that the simple variant does. By being able to apply the "dyn:call" operation to these objects, you retain all this helpful functionality after you somehow obtained a DynamicMethod object by name.
This would allow operations like this in a hypothetical JavaScript implementation that knows to compile "Function.call" down to "dyn:call":
// Retrieve "println" as a function
var println = Java.type("java.lang.System").out.println
Function.call(println, out, "The answer is")
Function.call(println, out, 42)
So far, so good. Where the question comes in: how should these instances be accessed?
One somewhat obvious approach (one used by the example above) is to expose them as POJO properties. Therefore, "dyn:getProp:println" executed on an instance of "java.io.PrintStream" will return an overloaded dynamic method, that if subsequently used as the receiver of the "dyn:call" operation, can be invoked.
While very appealing on the surface, the approach has the small issue of namespace confusion. Consider this Java class:
public class Confused {
public int getColor() { ... }
public void color()
}
Quick, what should "dyn:getProp:color" operation do when applied to an instance of Confused? Should it invoke "getColor()" and return its value, or should it return a DynamicMethod representing the "color" method?
Note that we don't have this confusion with method invocation operations as "dyn:callPropWithThis:color" will invoke "color()", and both "dyn:callPropWithThis:getColor" and "dyn:getProp:color" will invoke "getColor()".
You could argue that a Java class with an API like above is horrible, and I would agree with you. On the other hand, I don't feel like being a judge of API design and constrain the Dynalink's POJO MOP to a subset of classes that expose a "well-designed API".
I could deliberately not expose these objects in the MOP at all, that is, none of the existing "dyn:" operations would retrieve such objects, ever. However, if you want this functionality in your own language, I could give you a Java API in Dynalink, maybe BeansLinker.getDynamicMethod(Object obj, String name)
, and you'd be at liberty to peruse this any way you wish in your own language linker. You could expose the functionality in some explicit object that acts as a gateway to the underlying Java platform (I use an object named "Java" throughout my examples), i.e.
var out = System.out
// Here's how you'd get a function and call it
var println = Java.getMethodAsFunction(out, "println")
Function.call(println, out, "Hi on stdout!")
Function.call(println, System.err, "Hi on stderr!")
// You could do receiver-bound functions too!
var printlnToOut = Java.getBoundMethodAsFunction(out, "println")
printlnToOut("The answer is")
printlnToOut(42)
You could add a small specialized linker either before or after BeansLinker (depending on what behavior you want to give priority to) that answers "dyn:getProp:" requests, and delegates to this API. This would also allow your language to make other informed decisions, such as whether a Java method retrieved through "dyn:getProp" on an object should be bound to that object or not. Again, language semantics remain more flexible. I could actually provide you with a prefabricated "MethodsAsPropertiesLinker" that you can just drop into your linker chain at the appropriate point.
Admittedly, I like this option best as this feels most like giving language implementers a useful tool, and letting them decide how to use it.
We could also expose them through another object accessible as virtual properties. I.e. we could add virtual properties methods
to objects. Therefore, you could obtain these dynamic methods i.e. as
var println = somePrintStream.methods.println
or
var getSignatureInstance = Java.type("java.security.Signature").methods.getInstance
While this exposes them through the MOP (you'd basically do a "dyn:getProp:methods" on an object, then "dyn:getProp:println" on that (Dynalink-internal) object. The advantage of this is that they get readily exposed through the MOP, and your language doesn't need to do anything to access them. The drawback is that it is a bit more verbose, and still allows for a name clash, most notably with "java.lang.Class.getMethods()" method, as the value of "dyn:getProp:methods" executed on a Class object becomes ambiguous.
Tell me what you think.
You might not have been aware of it, but this wiki is publicly editable, so as long as you're a GitHub user, you can add your comments into this page, in the venerable style of programming discussion pages on the first ever wiki, the C2 WikiWikiWeb. Feel free to go below the horizontal rule at the bottom of this paragraph, all the way past the last comment, add another horizontal rule, and leave your comment. I will preserve those comments without changes as long as they're on topic and the tone is professional. You can add comments in the main text itself too, but I will periodically perform some gardening on those and am not promising anything with regard to those - they might get incorporated, or they might fall victim to landscaping. I encourage you to sign your edits/comments like this -- Attila. (Yes, I know the autorship can be traced back through the page edit history. It's just nice to have it spelled out on the page itself.)
Comments go here