-
Notifications
You must be signed in to change notification settings - Fork 7.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JRuby examples don't work (JRuby picks the wrong overloaded method) #320
Comments
Glad to have you involved, we haven't had much use of JRuby (that I'm aware of) so I'm not surprised we have some issues. I would appreciate your involvement. Can you check out code from this pull request (#319), build the rxjava-jruby jar and try it? We are working on a very different approach in this pull request for language adaptors that is statically typed. As for the |
It works better, but not perfect. JRuby seems to pick a working overload, but it still warns about there being multiple overloads (which is hard to fix without adding JRuby-metadata to the An upside is that you can skip the Since there will be a version generated to work specially for JRuby, how much can the code generation things add? Ideally it would add JRuby metadata to the class to make it work as a JRuby native extension. Can you give me any hints on how to dig into that? Where should I start to look in the code to understand the code generation? |
If the generated @JRubyClass(name="Rx::Observable")
public class Observable<T> extends RubyObject {
@JRubyMethod(meta = true, rest = true)
public static <T> Observable<T> from(ThreadContext ctx, IRubyObject receiver, IRubyObject[] args) {
// ...
}
@JRubyMethod(name = "subscribe")
public IRubyObject subscribeRuby(ThreadContext ctx, Block block) {
// ...
} There would be some more code needed that registered the class with the runtime, and created the |
Can you take a look at pull request #323 and suggest how to re-add JRuby support correctly? We have decided against doing byte-code generation. We can not add anything (such as the annotations in your example) to rxjava-core, it needs to be in a separate submodule, similar to Groovy, Scala and Clojure. So this means using something like extension methods, implicits, or other such tools if JRuby supports them. |
I will have a look. |
With some small patches at least some of the basics work: require 'rxjava-core/build/libs/rxjava-0.10.1-SNAPSHOT.jar'
class Java::Rx::Observable
java_alias :subscribe, :subscribe, [Java::RxUtilFunctions::Action1.java_class]
end
module Rx
include_package 'rx'
end
o = Rx::Observable.from([1, 2, 3])
o.map { |n| n * 2 }.subscribe { |n| puts(n) } I haven't tested any more than that, but it looks like it could work. |
#323 should be |
Good catch, I'm not used to gradle, or even having to compile stuff, so I didn't clean up from the last build I did so the old jars were still around. The Ruby patch works with 0.11.0 too (which means that those small patches would have make the old version work too, so that's good to know). Without the patch it works as with #319, the code runs but it prints a warning about not finding the right overloaded method. The boring part of doing it this way is that each method on Observable, and any other classes needed for interoperability will need to be annotated with |
Does JRuby have a way to apply these aliases programmatically? For example, in Groovy we use reflection to determine all methods that need extensions and then programmatically create all of the Otherwise every time a new method is added to core someone will have to maintain the JRuby java_alias mappings. |
Maybe, I'm not sure, I'll have to look into more about how JRuby's native extensions work. So far I've only done it by creating classes and adding metadata, but I guess that it should be possible to do it on existing classes to somehow. If I understand the code you linked to correctly it's looking through the All that the So, yes, probably, maybe, hopefully, but I'm not sure exactly how right now. |
This isn't complete yet, but here's a first pass at an implementation I think will work. @benjchristensen can you take a look and let me know if this is the direction you're looking for? It's largely cribbed from the Groovy implementation. |
I'm getting warnings about overloads. This is my code: require 'rxjava-core/build/libs/rxjava-core-0.14.2-SNAPSHOT.jar'
require 'language-adaptors/build/libs/language-adaptors-0.14.2-SNAPSHOT-sources.jar'
module Rx
include_package 'rx'
end
o = Rx::Observable.from([1, 2, 3])
o.map { |n| n * 2 }.subscribe { |n| puts(n) } and this is the output (JRuby 1.7.4):
|
Just to be clear, the commit I posted doesn't work yet. I just want to make sure that the approach is sane before investing more time in it. |
Ok. Yeah, it might work. Not sure how well the generated methods will work, though: they are added on the classes, but call And either way, once you call I think a better way to solve the problem is to use |
You're right, the I tried the I'm a relative newbie at JRuby, so totally possible I'm doing something wrong. But from my basic understanding I don't understand how |
I spent some more time researching this evening and I think the easiest path forward is to leverage the JRuby method dispatching as much as possible. Outside of the The first thing the dispatch logic checks for when trying to find a method signature match is whether the Java class of the argument provided is an exact match with the parameter type specified in the method signature (see: https://github.com/jruby/jruby/blob/master/core/src/main/java/org/jruby/java/dispatch/CallableSelector.java#L321). While it isn't guaranteed that this will always be true (JRuby could change the dispatch logic), I think it's a safe bet that it will continue to preference exact class matches when selecting overloads. I think that means that as long as we pass in arguments that implement the exact interfaces JRuby should have a much easier time finding the matching method signature. Assuming that's true, I think we should implement an algorithm like the following on load:
Then I think we should implement the following to occur at runtime:
We should be able to do this using Under this scenario most of the hard dispatch logic remains in Java: the only additional runtime things we're doing in Ruby are a) a check to see if the argument is a It's clear you've thought about this quite a bit @iconara; anything stick out to you as suspect here? |
It isn't the cleanest thing in the world, but ragalie@957af11 seems to be working correctly. This code no longer causes an ambiguous method warning: require 'rxjava-core/build/libs/rxjava-core-0.14.2-SNAPSHOT.jar'
require 'language-adaptors/rxjava-jruby/build/libs/rxjava-jruby-0.14.2-SNAPSHOT.jar'
require 'language-adaptors/rxjava-jruby/src/main/ruby/rx/lang/jruby/interop'
o = Java::Rx::Observable.from([1, 2, 3])
o.map { |n| n * 2 }.subscribe { |n| puts(n) } I'm going to clean it up as best as I can then open a PR. I'm pretty new to JRuby, though, and I'm sure there are some ways to simplify what I've done, so hopefully someone can help me out with that. In particular I'd love to be able to leverage the built-in JRuby proxying (telling it which interface to proxy) instead of the clunky ones in the commit. I also don't know what to do with the Ruby code. Should that stay in the JAR and just need to be required manually? Or should it be pulled out to a gem? I'm not sure what's idiomatic. Thanks! |
@ragalie I'm sure it can be done more elegantly, but it would take a lot of time, and I don't know very much more about the details of JRuby's Java integration to say for sure how to do it. It's better to get something that works and improve it later than trying to find the optimal solution now. There's a way to package a JAR that makes JRuby run code when it is require'd from Ruby code. You need to stick a special class at the root of the JAR (here's an example: https://github.com/iconara/msgpack-jruby/blob/master/ext/java/MsgpackJrubyService.java). It's used to load JRuby native extensions (i.e. Java code that creates JRuby modules and classes), but in this case it could be used to automatically run the interop code (which can be loaded from within the JAR). Another option would be to make the the interop code the main entrypoint for Ruby, and for it to load The benefit of the former solution would be that you could ship it all as just the JAR, but the latter is simpler to maintain and is how many JRuby wrappers for Java libraries work. If it's ever going to get any kind of adoption in the Ruby world the library must be packaged as a gem. |
I implemented JRuby support in #422. Let me know what you all think and if there's anything that doesn't seem to be working correctly. Thanks! |
Awesome @ragalie! I'm taking a look now and will merge it into master or iterate with you on it if there are changes needed. |
The JRuby adapter doesn't work because JRuby can't figure out which overloaded
Observable#subscribe
method to pick. It ends up pickingObservable#subscribe(Map<String, Object>)
, which raises an error because the argument doesn't respond right:the code above prints the following errors in JRuby 1.7.4:
in JRuby 1.6.8 it prints a less verbose version of the same error.
Notice the line which reads "onNext". That's actually the
puts
fromlambda { |x| puts x }
in action. JRuby wraps the lambda in something that looks like aMap
, and then when RxJava callsget
on that map JRuby callscall
on the lambda.So if you modify the example code to read
lambda { |x| lambda { |y| puts y } }
this is what happens:Which kind of works, but it's not as smooth as the ideal API would be:
But that would probably require a real JRuby native extension (if I get the time I'll send you a pull request with one).
The text was updated successfully, but these errors were encountered: