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
end
foo = 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]))
end
rcopy (generic function with 92 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, "Bar")
unprotect(1)
r
end
bar = robject(:Bar, foo)
nothing # hide
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)
nothing # hide
Using @rput and @rget is seamless
foo2.x = 2.0
@rput foo2
R"""
foo2["x"]
"""
R"""
foo2["x"] = 3.0
"""
@rget foo2
foo2.x
Nested conversion
l = R"list(foo2 = foo2, bar = $bar)"
rcopy(l)