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, 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.x
3.0

Nested 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")