Custom Conversion
RCall supports an API for implicitly converting between R and Julia objects by means of rcopy and robject.
To illustrate the idea, we consider the following Julia type
mutable struct Foo
x::Float64
y::String
endfoo = Foo(1.0, "hello")
bar = R"""
bar <- list(x = 1, y = "hello")
class(bar) <- "Bar"
bar
"""R to Julia direction
The function rcopy and rcopytype are responsible for conversions of this direction. First we define an explicit converter for VecSxp (SEXP for list)
import RCall.rcopy
function rcopy(::Type{Foo}, s::Ptr{VecSxp})
Foo(rcopy(Float64, s[:x]), rcopy(String, s[:y]))
endrcopy (generic function with 96 methods)The convert function will dispatch the corresponding rcopy function when it is found.
rcopy(Foo, bar)
convert(Foo, bar) # calls `rcopy`To allow the automatic conversion via rcopy(bar), the R class Bar has to be registered.
import RCall: RClass, rcopytype
rcopytype(::Type{RClass{:Bar}}, s::Ptr{VecSxp}) = Foo
foo2 = rcopy(bar)Julia to R direction
The function RCall.sexp has to be overwritten to allow Julia to R conversion. sexp function takes a julia object and returns an SEXP object (pointer to [Sxp]).
First we define an explicit converter from Julia type Foo to R class Bar
import RCall: sexp, protect, unprotect, setclass!, RClass
function sexp(::Type{RClass{:Bar}}, f::Foo)
r = protect(sexp(Dict(:x => f.x, :y => f.y)))
setclass!(r, sexp("Bar"))
unprotect(1)
r
end
bar = robject(:Bar, foo)Remark: RCall.protect and RCall.unprotect should be used to protect SEXP from being garbage collected.
To register the default conversion via robject(foo), we need to define sexpclass
import RCall.sexpclass
sexpclass(f::Foo) = RClass{:Bar}
bar = robject(foo)Using @rput and @rget is seamless
foo2.x = 2.0
@rput foo2
R"""
foo2["x"]
"""RObject{VecSxp}
$x
[1] 2
R"""
foo2["x"] = 3.0
"""
@rget foo2
foo2.x3.0Nested conversion
l = R"list(foo2 = foo2, bar = $bar)"RObject{VecSxp}
$foo2
$y
[1] "hello"
$x
[1] 3
attr(,"class")
[1] "Bar"
$bar
$y
[1] "hello"
$x
[1] 1
attr(,"class")
[1] "Bar"
rcopy(l)OrderedCollections.OrderedDict{Symbol, Any} with 2 entries:
:foo2 => Foo(3.0, "hello")
:bar => Foo(1.0, "hello")