-
Notifications
You must be signed in to change notification settings - Fork 186
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
Integrate foreign objects better with Ruby objects #2149
Comments
It might be better to inherit from the core Ruby types actually, like |
In theory, you could generate all sorts of Ruby classes each with a different combination of traits. On class lookup, TruffleRuby could return the best possible fit depending on the interop properties of the foreign object (e.g. |
Right, it would be nice if they have a name though, for inspection/debugging. But we could generate the classes dynamically from Java or from Ruby I suppose. I think initially I'd keep it static while we have few traits handled in Ruby.
That's a good point, handling foreign object specially in |
I tried to implement some of the ideas, but had the following two problems:
|
* By giving foreign objects a proper Ruby class. * Move the logic for all special methods on foreign objects (Implicit polyglot API) to regular Ruby methods of Polyglot::ForeignObject and Polyglot trait modules. * Automatically generate Polyglot::Foreign* classes composing the InteropLibrary traits. * Simplify Polyglot::ForeignObject#respond_to? now that most are defined as Ruby methods. * Fixes oracle#2149 * From oracle#2153 Co-authored-by: Benoit Daloze <benoit.daloze@oracle.com>
* By giving foreign objects a proper Ruby class. * Move the logic for all special methods on foreign objects (Implicit polyglot API) to regular Ruby methods of Polyglot::ForeignObject and Polyglot trait modules. * Automatically generate Polyglot::Foreign* classes composing the InteropLibrary traits. * #[] on foreign arrays now return nil if out of bounds like Array#[]. * Foreign arrays now have all methods of Enumerable and many methods of Array. * Simplify Polyglot::ForeignObject#respond_to? now that most are defined as Ruby methods. * Fixes oracle#2149 * From oracle#2153 Co-authored-by: Benoit Daloze <benoit.daloze@oracle.com>
* By giving foreign objects a proper Ruby class. * Move the logic for all special methods on foreign objects (Implicit polyglot API) to regular Ruby methods of Polyglot::ForeignObject and Polyglot trait modules. * Automatically generate Polyglot::Foreign* classes composing the InteropLibrary traits. * #[] on foreign arrays now return nil if out of bounds like Array#[]. * Foreign arrays now have all methods of Enumerable and many methods of Array. * Simplify Polyglot::ForeignObject#respond_to? now that most are defined as Ruby methods. * Fixes oracle#2149 * From oracle#2153 Co-authored-by: Benoit Daloze <benoit.daloze@oracle.com>
* By giving foreign objects a proper Ruby class. * Move the logic for all special methods on foreign objects (Implicit polyglot API) to regular Ruby methods of Polyglot::ForeignObject and Polyglot trait modules. * Automatically generate Polyglot::Foreign* classes composing the InteropLibrary traits. * #[] on foreign arrays now return nil if out of bounds like Array#[]. * Foreign arrays now have all methods of Enumerable and many methods of Array. * Simplify Polyglot::ForeignObject#respond_to? now that most are defined as Ruby methods. * Fixes oracle#2149 * From oracle#2153 Co-authored-by: Benoit Daloze <benoit.daloze@oracle.com>
This is now implemented, see #2153 (comment) and 4b6964f for more details. p Truffle::Interop.to_java_array([1, 2, 3]) # => #<Polyglot::ForeignArray[Java] int[]:0x23eee4b8 [1, 2, 3]>
obj = Truffle::Debug.foreign_pointer_array_from_java(Truffle::Interop.to_java_array([1, 2, 3]))
p obj # => #<Polyglot::ForeignArrayPointer 0x0 [1, 2, 3]>
p obj.class.ancestors # => [Polyglot::ForeignArrayPointer, Polyglot::ArrayTrait, Enumerable, Polyglot::PointerTrait, Polyglot::ForeignObject, Object, Kernel, BasicObject]
p obj.map { _1 * 2 } # => [2, 4, 6] Also foreign objects nicely display in irb now, while it used to be a problem with |
Nice! What happens if traits have overlapping methods or aren't there any? |
There is no Hash entries trait yet, but it should be added soon. truffleruby/src/main/ruby/truffleruby/core/truffle/polyglot.rb Lines 99 to 121 in eb057e7
That one is easy to decide as arguments have different types (integer array indices vs String/Symbol member names). I think we'll have to choose which takes precedence between Hash entries and Array elements. Hash entries will also hide members from |
Introducing a notion of precedence is, of course, one possible way forward. However, it's always going to cause some incompatibilities. What if, for example, the users passes an object into a Ruby method and the wrong interface is exposed? Similar to target type mappings on the level of the host language, maybe there's a way to give developers some control over such precedences used in TruffleRuby. Nonetheless, I do believe that some user intervention is always going to be needed. In TruffleSqueak, users can control what kind of interface their objects expose: |
I think we want to maximize portability (as in existing Ruby code should just work with foreign objects) as @chumer would say (and obj[] is used mostly for Array & Hash in Ruby), and a foreign hash seems more specific than just "an object with members". Members are typically accessed with obj.foo/obj.foo= anyway (it looks nicer, and is more concise), so there is probably very little overlap in practice. Note there is already potential overlap betwen e.g. Enumerable methods and JS array methods (e.g., Yes, one way could be to have some explicit wrapper that only exposes one given trait. |
Hang on, I thought users are not supposed to use |
Still changing my mind about that, because sometimes there seems to be no other way, or sometimes being explicit feels better/safer. Notably in ExecJS, e.g., there is currently no way to express Maybe we should simply rename |
I guess the main reason why it's not exposed in all guest languages is that it's still being evolved a lot and it's hard to change things if users depend on them. In terms of naming, why not call it |
I guess InteropLibrary won't change in incompatible ways, it would likely break all languages.
Maybe, the main reason is to avoid potential name clashes by limiting the number of top-level constants added by TruffleRuby (no choice for
Yes it does. I managed locally to avoid using |
True :)
Since there already is a
Mh, well I don't know what specific ExecJS problem you're currently working on, but how do you identify instance variables from |
A good question. Since in Ruby there is
So there I just filter those explicitly with |
Hash, Iterable and Iterator traits added in 21d0dc2 (with HashTrait#[] winning over ArrayTrait#[]). |
* By giving foreign objects a proper Ruby class. * Move the logic for all special methods on foreign objects (Implicit polyglot API) to regular Ruby methods of Polyglot::ForeignObject and Polyglot trait modules. * Automatically generate Polyglot::Foreign* classes composing the InteropLibrary traits. * #[] on foreign arrays now return nil if out of bounds like Array#[]. * Foreign arrays now have all methods of Enumerable and many methods of Array. * Simplify Polyglot::ForeignObject#respond_to? now that most are defined as Ruby methods. * Fixes oracle#2149 * From oracle#2153 Co-authored-by: Benoit Daloze <benoit.daloze@oracle.com>
Currently, foreign objects are handled specially in
OutgoingForeignCallNode
.They are given a Ruby class,
Truffle::Interop::Foreign
but actually methods are not looked there, all method calls are directly dispatched toOutgoingForeignCallNode
instead.I think a better design would be to have a set of modules and classes for foreign objects, and make foreign objects behave more like Ruby objects by using the normal Ruby method lookup.
When a Ruby method would not be found, we would check members with
isMemberInvocable
and eitherinvokeMember
orreadMember
, instead of callingmethod_missing
.So we could have something like
Polyglot::ForeignArray < Polyglot::ForeignObject < Object
andPolyglot::ForeignArray
would includeEnumerable
.Then we could easily define the logic in Ruby.
All Object , Kernel, and BasicObject methods should then also handle foreign objects.
Given that a foreign object might have multiple traits:
https://github.com/oracle/graal/blob/68ce20c37071cfa91146f437eb6ab6ccffbd0759/truffle/src/com.oracle.truffle.api.interop/src/com/oracle/truffle/api/interop/InteropLibrary.java#L129-L145
we should probably use modules to represent those traits that map to Ruby concepts.
So we'd have
module Polyglot::Trait::ForeignArray
,module Polyglot::Trait::ForeignProc
, etc.And we'd need classes to compose them like (an object must be the instance of a class, not of a module)
(just a sketch, we might want to think more about names)
foreign_object.class
would actually return one of these classes, and we'll need an efficient way to compute that as it would be used for every method call on a foreign object.Internally, to get the class of a foreign object, we'd probably want to add
RubyLibrary#getMetaClass()
andRubyLibrary#getLogicalClass()
and implement them for foreign objects.cc @rbotafogo @fniephaus @chumer
The text was updated successfully, but these errors were encountered: