-
Notifications
You must be signed in to change notification settings - Fork 60
support for passing custom types? #138
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
Comments
That's a good idea. We should be able to do this using dispatch somehow. |
I should say that though it's not documented, this is already possible in the Julia -> R direction by overloading The other direction is more complicated. At the moment we only dispatch on the R core "SEXPTYPE"s, but we could change this to dispatch on the class attribute. We would also have to figure out how to incorporate S4 classes and Reference Classes (though these seem to be less common in the wild). |
I think that might be worth giving a go, but I don't think I am up to the task of dealing with R's garbage collector references. |
One solution would be an "RCallRecipes" approach, being a lightweight repo which gets included in your package as a dependency, but doesn't require loading or installing RCall. |
I like that idea a lot. |
That, together with a system similar to FileIO.jl, would be very useful to automatically choose what type to use when getting a special class from R. |
I have been thinking about this a little, but still don't have a concrete way forward yet. As a reference (partly for myself) here are a couple of pages on how R's S3 objects work:
The main challenge with the R -> Julia direction is that R S3 objects can have multiple classes, set via the class attribute: class(x) <- c("aa", "bb", "cc") (Complicating matters is that there are some "fallback" classes for when no "class" attribute is defined, e.g. R's generic methods then check if One way is to add an extra "type" argument to rcopy(::Type{Matrix{Float64}}, ::RClass{:matrix}, x::RObject{RealSxp}) = ...
rcopy(::RClass{:matrix}, x::RObject{RealSxp}) = ... and then define the generic function rcopy{S}(x::RObject{S})
for class in getclasses(x)
classsym = symbol(class)}
if method_exists(rcopy, Tuple{RClass{classsym}, RObject{S}})
return rcopy(RClass{classsym}(), x)
end
end
return rcopy(RClass{:default}(), x)
end |
I understand your point, @simonbyrne, but the example is not a good one because "matrix" isn't a class in R. The Matrix package defines S4 classes for various types of matrices and a R was created to be "not unlike S" and these decisions go back to the original S formulation. |
Is it really necessary to generalize the function convert(::Type{Foo}, x::RObject)
# using rcopy function to access x
end |
@dmbates: while looking into it I realised that @randy3k Fair enough, but I would like to be able to get rid of the DataFrames dependency and this provides a path to do that. |
I like the idea to get rid of the dependency of DataFrames and all related packages. How about only keeping function convert(::Type{DataFrame}, x::RObject)
### ...
end |
@simonbyrne Anyone familiar with Julia's multiple dispatch and type system will recognize that S4 classes and methods are an elegant idea. However, the implementation is not quite as elegant. The |
@randy3k I think you need to Data frames are pretty fundamental to R and I think they warrant special consideration in |
IIUC this would allow getting rid of the |
@nalimilan, @quinnj Would As I said earlier, I think that data frames are sufficiently fundamental to R that they should be given special consideration in |
Seeing how DataStreams currently depends on DataFrames, no, it would not be a more light-weight dependency :) That said, the DataFrames code in DataStreams actually belongs in DataFrames, and DataStreams should really be it's own standalone package with no dependencies. It's on my todo list to get all hte code moved out. |
@dmbates You are right! It should be My logic was to keep EDIT |
This is what I am thinking:
So now all the conversions are explicit. To allow implicit conversion, we can then define a version of |
I think that seems reasonable, though should we define them as @nalimilan My idea was that we would have a lightweight package, called say |
My bad, I just could not recall if constructor calls |
Hey thanks for the progress here, the direction this takes looks really promising. |
@simonbyrne Can't this be alleviated by noting the order of class attributes matters in R: normally to implement some kind of mechanism resembling inheritance during S3 dispatch of methods. So can one route to solving this be to say, well much as with julia, the actual concrete type/class (which I think is usually the first element of Edit: Ah actually your way is a lot better, I'm clearly tired as I didn't see it on my first pass:
|
@simonbyrne I'm certainly interested in your earlier offer to give pointers for overloading Also, in the longer term, we are developing a package in R and Julia simultaneously, and validating them against each other. At the moment, this is working okay(ish) using Finally, I'd like to do this automatically via
would seem like a better approach for consistency if ambiguities exist, even if all it does is call the copy constructor. This would also help with built in constructors like |
Ah, just realised the significance of your |
For the |
If there's a definite point at which RCallRecipes will no longer be needed which is only a year away, then something quite hacky may suffice... and talking of hacky, I also wondered whether it's possible for the same repository to provide two different packages by having two entries in |
I now have working R translation code for my package using j = MyType()
@rput j it should be able to recognise that there's a way of translating to R? Instead I have to do: j = MyType()
r = RObject(j)
@rput r which seems unnecessarily clumsy. I also presume that the suggestion of @simonbyrne above to use |
Is there any error message when you do |
Oops, sorry. No problem. The package is here, the branch with the RCall code in is called julia> using RCall
WARNING: Method definition ==(Base.Nullable{S}, Base.Nullable{T}) in module Base at nullable.jl:238 overwritten in module NullableArrays at /Users/richardr/.julia/v0.6/NullableArrays/src/operators.jl:128.
julia> using Phylo
R> library(ape)
julia> cd(Pkg.dir("Phylo", "src"))
julia> include("rcall.jl")
sexp (generic function with 114 methods)
julia> nu = Nonultrametric(5);
julia> jt = rand(nu)
NamedTree phylogenetic tree with 9 nodes and 8 branches
Leaf names:
String["tip 1", "tip 2", "tip 3", "tip 4", "tip 5"]
julia> @rput jt
ERROR: MethodError: no method matching protect(::RCall.RObject{RCall.VecSxp})
Closest candidates are:
protect(::Ptr{S<:RCall.Sxp}) where S<:RCall.Sxp at /Users/richardr/.julia/v0.6/RCall/src/types.jl:297
Stacktrace:
[1] setindex!(::Ptr{RCall.EnvSxp}, ::Phylo.BinaryTree{Phylo.LeafInfo,Void}, ::Symbol) at /Users/richardr/.julia/v0.6/RCall/src/methods.jl:452
[2] setindex!(::RCall.RObject{RCall.EnvSxp}, ::Phylo.BinaryTree{Phylo.LeafInfo,Void}, ::Symbol) at /Users/richardr/.julia/v0.6/RCall/src/methods.jl:461
julia> rt = RObject(jt)
RCall.RObject{RCall.VecSxp}
Phylogenetic tree with 5 tips and 4 internal nodes.
Tip labels:
[1] "tip 1" "tip 2" "tip 3" "tip 4" "tip 5"
Rooted; includes branch lengths.
julia> @rput rt
RCall.RObject{RCall.VecSxp}
Phylogenetic tree with 5 tips and 4 internal nodes.
Tip labels:
[1] "tip 1" "tip 2" "tip 3" "tip 4" "tip 5"
Rooted; includes branch lengths.
R> rt
Phylogenetic tree with 5 tips and 4 internal nodes.
Tip labels:
[1] "tip 1" "tip 2" "tip 3" "tip 4" "tip 5"
Rooted; includes branch lengths. |
@randy3k Please let me know if you need more. As I'm confident is obvious from the code, I have no idea what I'm doing with the RCall interface - I just played with things that I found in the package until I got the kind of behaviour I wanted. Any suggestions are very welcome! |
Thanks for providing the example. Unfortunately, I won't have time to look it up until next week. |
No problem, thanks for being interested anyway! |
I have quickly skimed over your package. The issue was that |
Ah, fab - thanks for the pointer. And you're right - I'm struggling with it. I tried calling |
There is a version of
|
@randy3k - Thanks very much for all of your help. That all seems to be working seamlessly in the Julia -> R direction now, and it makes a lot more sense. Is there any problem with the protection from R garbage collection when I repeatedly overwrite a variable (in a loop) that I'm exporting to R? I do that a lot in testing between our R and Julia packages, and I don't want to be leaking memory... hopefully the R gc happens automatically when the underlying julia objects are garbage collected? |
When an RObject is created, a finalizer is also registered so that when it is freed by the Julia GC, its memory in R will be released too. But such mechanism is a bit heavy. Within a function stack frame, the memory should be protected manually using the protect and unprotect pair. It is just not efficient to have RObjects everywhere with a function. |
Okay, thanks. So now I have conversion working transparently from Julia to R, I'm still a bit away from the opposite direction: julia> using RCall
WARNING: Method definition ==(Base.Nullable{S}, Base.Nullable{T}) in module Base at nullable.jl:238 overwritten in module NullableArrays at /Users/richardr/.julia/v0.6/NullableArrays/src/operators.jl:128.
julia> using Phylo
julia> include(Pkg.dir("Phylo", "src/rcall.jl"));
R> library(ape)
R> rt <- rtree(4)
julia> jt=NamedTree(R"rt")
NamedTree phylogenetic tree with 7 nodes and 6 branches
Leaf names:
String["t2", "t1", "t4", "t3"]
julia> @rput jt;
R> jt
Phylogenetic tree with 4 tips and 3 internal nodes.
Tip labels:
[1] "t2" "t1" "t4" "t3"
Rooted; includes branch lengths.
R> all.equal(rt, jt)
[1] TRUE Apart from the |
#192 has implemented a version of @simonbyrne's RClass idea. The idea is an user should define an explicit However the default rcopytype{S <: Sxp}(::Type{RClass{:Bar}}, s::Ptr{S}) = Foo This will allow |
Hi,
RCall offers a great many standard conversions - julia arrays to R arrays, R lists to julia Dicts etc. But there are also numerous more complex types defined within R and julia packages. Could RCall support an API for implicitly converting between R and julia objects by allowing users to specify a conversion function?
An example: both languages have (several) representations for defining phylogenies: BioJulia has e.g. the Phylo submodule, and the ape R package has the phylo type. It is possible to use RCall code to split the julia Phylogeny type into components (arrays and such), pass them to R, then construct an ape phylo object. It is thus easy to define a conversion function.
The ideal method would be to be able to define such a method using an RCall standard API, putting them either in the relevant julia package, in RCall or an RCall_typeextensions package, and then after defining this method being able to use the conversion implicitly, e.g.
The text was updated successfully, but these errors were encountered: