-
-
Notifications
You must be signed in to change notification settings - Fork 53
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
Proposal: jdcall and jtypeforclass #93
Comments
JVM internals aren't that elegant, sometimes you really, really want to call a very specific method. For example, in Spark.jl I once encountered two methods of the same object with the same list of parameter types, but with different return types (which was a result of tricky inheritance and/or Java/Scala compiler differences). Anyway, the only way to call exact method I wanted was to pass exact list of types to JNI, so it's unlikely we will be able to replace Are you concerned with the need to specify argument types? If so, have a look at #91 which provides a more high-level API with Java-like syntax and automatic
And at the same time it will preserve low-level API of |
ANSWER: yes, if we meet case like this (multiple match found), the
@spark df.groupBy(["match_type"]...).agg([
@col(lit(int_obj(1)).count().alias("count")),
@col(won.when(bool_obj(true)).count().alias("won"))]...).sort([@col won.desc()]).toJSON().take(200) Notice here we do not have a line
|
TL; DR I would love |
First of all, this looks really great! Having said that, I still think you and @zot work on a very similar approaches and your Spark example could be a good demo for JProxy as well. What I'm concerned with is that we may end up in multiple intersecting APIs which would be confusing to occasional users. I like simplicity and transparency of To fix it, one could add a method cache and bind it to an object representing loaded class. At this point it starts looking similar to JProxy approach (though Bill may add more context - I only roughly track implementation changes). I hope this doesn't discourage you - I really enjoy the described future, just want to keep implementation sound and robust so we could maintain it for many years without breaking things. |
By the way, regarding method signatures with different return types:
There are now 2 methods with the same signature except for return type, so your Let's make it a bit more interesting and redefine A.java as:
Recompile A.java, but not B.java. This may happen e.g. when combing in runtime different versions of JARs.
So far so good.
Still correct - method from B.
WAT? Now we have 2 methods with exactly the same name and argument list, but with even incompatible return types! You might think it's just a contrived example, but as I mentioned earlier I've encountered something very similar in practice in Spark.jl - I spent a couple of hours figuring out correct signature for jcall when even |
Interesting point. I think the limit would also apply to b=JProxy(@jimport(B)(()))
b.foo() # should it be 42 or true? For the drawback that calling "cached methods here"
function jdcall_cached end
function jdcall_cache_clear!()
for m in methods(jdcall_cached)
Base.delete_method(m)
end
end
function jdcall_cache(obj::Union{JavaObject{C}, Type{JavaObject{C}}}, name::AbstractString, args...) where C
matchmethods = findmethod(obj, name, args...)
if length(matchmethods) == 0
allmethods = listmethods(obj, name)
candidates = join(allmethods, "\n ")
error("no match methods $name for $obj, candidates are:\n $candidates")
elseif length(matchmethods) > 1
candidates = join(matchmethods, "\n ")
error("multiple methods $name for $obj, candidates are:\n $candidates")
end
matchmethod = matchmethods[1]
rettype = jtypeforclass(getreturntype(matchmethod))
argstype = tuple(map(jtypeforclass, getparametertypes(matchmethod))...)
# cache the method found
args_julia = Symbol.(string.("arg", 1:length(args)))
params_julia = [:($name::$type) for (name, type) in zip(args_julia, typeof.(args))]
eval(:(function jdcall_cached(base::Union{JavaObject{$(QuoteNode(C))}, Type{JavaObject{$(QuoteNode(C))}}}, ::Val{$(QuoteNode(Symbol(name)))}, $(params_julia...))
jcall(base, $name, $rettype, $argstype, $(args_julia...))
end))
return rettype, argstype
end
function jdcall(obj::Union{JavaObject{C}, Type{JavaObject{C}}}, name::AbstractString, args...) where C
if !applicable(jdcall_cached, obj, Val(Symbol(name)), args...)
rettype, argstype = jdcall_cache(obj, name, args...)
return jcall(obj, name, rettype, argstype, args...)
end
@info("using cached $(@which jdcall_cached(obj, Val(Symbol(name)), args...))")
return jdcall_cached(obj, Val(Symbol(name)), args...)
end If you use some class heavily, the There remains some issues.
|
I realized that the dispatch/findmethod relies on convert(x, y) = error("Try to convert $(y) to $(x), this should not happen") (at the same time Maybe we should make "julia to JavaObject" convert more robust.
|
We may also encounter issues with this piece:
if we try to use newly defined method at the same world age as we called
|
There still an option 4 that use global I think the issue also applies for JProxy(@jimport(java.util.ArrayList)(())).getClass().getClassLoader().toString() when should I make the proxy for According to the author of |
Yes, it is doing dynamic dispatch and gen.jl is not currently used but JProxy caches the method ids in JClassInfo structures so that saves a lot of work. Once you make a proxy, the class info is saved so it's around the next time you make one. JProxy only caches classinfo for its class and the ancestors (classes and interfaces). The classes of the method return types and arguments can cause subtypes of java_lang to be defined but not classinfo data. I.e. the method dictionaries are only populated for the JProxy data's class and its ancestors, not for the return types or arguments of the methods it finds. Otherwise it would end up "faulting in" large parts of the JDK when you referenced just about anything (I think one of my earlier versions did cache classinfo for return types and arguments though). I'm not sure about the eval and invokelatest issue -- is it possible to defer code to run in a later Julia world? To solve a problem like this in JavaScript, I'd do something like: function makeDefs(defsFunc, thenFunc) {
defsFunc();
setTimeout(thenFunc, 1); // run thenFunc later
} Maybe yieldto() will work for that? If that works, then you can just defer later code whenever you need to make more definitions. In any case you can just use @Class(a.b.c) to create a static proxy for a.b.c and that will do all the caching. By the way, you can use a static proxy as a constructor, in addition to providing access to static members, like this: AL = @class(java.util.ArrayList)
arrayList1 = AL()
arrayList1.add(1)
println(AL(arrayList1)) |
I love the idea that use macro to do static dispatch. |
Thanks! I just expanded the idea of JavaCall's
I just added This will help because function classforname(name::String)
thread = jcall(JThread, "currentThread", JThread, ())
loader = jcall(thread, "getContextClassLoader", JClassLoader, ())
return jcall(JClass, "forName", JClass, (JString, jboolean, JClassLoader),
name, true, loader)
end |
JavaCall.jl/src/convert.jl
Lines 321 to 325 in d6c3678
JavaCall.jl/src/reflect.jl
Lines 163 to 168 in d6c3678
This method may help
and the narrow would be
Since
narrow
andclassforname
are both exported, so this may be a breaking change, although I think the behavior of the two methods now is strange when it comes to array and/or jint.The text was updated successfully, but these errors were encountered: