2

As the topic describes I am looking for a way to achieve this:

require("R6")
R6cls <- R6::R6Class("R6obj",
    public = list(val = 1, foo = function() "foo!")
)
# check this R6 class
r6o <- R6cls$new()
r6o

# <R6obj>
#  Public:
#    clone: function (deep = FALSE) 
#    foo: function () 
#    val: 1

# now the S4 part 
S4wR6slot <- function() new("S4wR6slot")
setClass("S4wR6slot", contains = "environment", # or maybe "R6" or "R6cls"? tried them all but none seemed to work
         slots = list(a = "R6cls", b = "character"))
   
# Currently gives
Warning message:
undefined slot classes in definition of "S4wR6slot": a(class "R6cls") 
# but you can still kind of use it 
s4r6 <- S4wR6slot()
s4r6

# An object of class "S4wR6slot"
# <environment: 0x00000179de2da628>
# Slot "a":
# NULL
#
# Slot "b":
# character(0)
s4r6@b <- "text" # works nicely 
# BUT trying to assign to that env slot 
s4r6@a <- r6o

# will throw these "ugly" errors 
Error in (function (cl, name, valueClass)  : 
  c("assignment of an object of class “R6obj” is not valid for @‘a’ in an object of class “S4wR6slot”; is(value, \"R6cls\") is not TRUE", 
"assignment of an object of class “R6” is not valid for @‘a’ in an object of class “S4wR6slot”; is(value, \"R6cls\") is not TRUE")

Playing around with setOldClass() also did no good. Maybe there is simply no way to make that work - just asking if someone has an idea? Or maybe even achieved this - otherwise maybe I did not rtfm well enough and one can point me to the documentation, where it says thou' shall not try to make this work?

GWD
  • 1,387
  • 10
  • 22
  • @Allan Cameron - I have the more complex example (how it all started) with two packages (and an R6 Singleton) involved here - in case you or other SO users are interested https://github.com/tidylab/R6P/issues/19#issuecomment-1152085454 – GWD Jun 10 '22 at 08:19

2 Answers2

1

There is a way to have an R6 object inside an S4 class, but involves tricking the S4 system by defining an S4 class called 'R6'. This will prevent the methods package complaining that class R6 doesn't exist. Fortunately, it doesn't actually check whether the 'R6' object in the prototype is of the S4 type, allowing you to stick an actual R6 object in there.

library(R6)

R6cls <- R6::R6Class("R6obj",
                     public = list(val = 1, foo = function() "foo!")
)

setClass('R6')

setClass("S4wR6slot", slots = list(a = 'R6', b = "character"),
         prototype = list(a = R6cls$new(), b = 'Hello'))

S4wR6slot <- function() new("S4wR6slot")

myS4 <- S4wR6slot()

class(myS4)
#> [1] "S4wR6slot"
#> attr(,"package")
#> [1] ".GlobalEnv"

isS4(myS4)
#> [1] TRUE

myS4@a
#> <R6obj>
#>   Public:
#>   clone: function (deep = FALSE) 
#>     foo: function () 
#>       val: 1
Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • To happy - too soon -> that thing behaves very erratically; not finding the R6 constructor @importFrom does not cut it etc. ... I will still keep the answer sort of "accepted" because in some setups ie when sticking to some package loading order etc it does seem to work but sadly not "in general" – GWD Jun 09 '22 at 14:22
  • `@Allan Cameron` More precisely the tricking works for interactive scenario BUT the `devtools::document()` then (still) breaks on that solution so it does not stand up to my real life situation. I mean I was able/can trick `document()` by including a `require(pkgwR6class)` somehow - but that kind a feels 'dirty' :-) – GWD Jun 09 '22 at 14:41
  • @GWD so you can't declare the dummy R6 class and document it with Roxygen2? – Allan Cameron Jun 09 '22 at 14:44
  • tbh it does not look like it's a documentation issue error I get is `Error in base::get(x, ...) : object 'myR6ClassFromOtherPackage' not found` as if no matter what I do the object looks and acts as if never exported from that original R6 class definition package I have `importFrom` directives in there and according to the R6 maintainers that should be sufficient, but it simply won't fly. Putting a `require` or `library` call for that imported package in there somewhere solves it (for now) but as mentioned above - does not feel 'right' :-) – GWD Jun 09 '22 at 16:54
  • Final remark: since those `require` and `library` calls to package dependencies have been getting more common lately I have (re-)discovered the `Depends:` section in the `DESCRIPTION` file as a potential solution to avoid those `require` calls directly in the code. – GWD Jul 01 '22 at 21:02
1

I have also found a solution for this simple example class that seems to work - BUT SADLY that solution interestingly failed/fails for more complex R6 classes.

Adding that example solution here for completness

require(R6)
R6cls <- R6::R6Class("R6obj",
                     public = list(val = 1, foo = function() "foo!")
)
# check this R6 class
r6o <- R6cls$new()
r6o

# <R6obj> # <- I SUDDENLY NOTICED THIS on a second look! 
#   Public:
#     clone: function (deep = FALSE) 
#     foo: function () 
#    val: 1

S4wR6slot <- function() new("S4wR6slot")

setClass("S4wR6slot", contains = "environment",
         slots = list(a = "R6obj" , b = "character"))
# list(a = "R6obj" ... is what did the "magic"

s4r6 <- S4wR6slot()
s4r6

# An object of class "S4wR6slot"
# <environment: 0x000002520292f470>
# Slot "a":
# NULL
#
# Slot "b":
# character(0)

s4r6@b <- "text"
s4r6@a <- r6o # THIS THEN WORKED/WORKS! 

class(r6o)
# [1] "R6obj" "R6" # I simply used that "R6obj" in the S4 definition above
is(r6o, "R6")
# [1] TRUE
is(r6o, "R6obj")
# [1] TRUE

But somehow that "Robj" class (type) gets lost in more complex settings ie I am using a R6 class defined in one package in another package and then that "R6obj" is no longer available for usage. :-(

GWD
  • 1,387
  • 10
  • 22