I have a R6 class BarContainer
that contains a vector of Bar
, where Bar
is another R6 class. I would like to implemented a method subset
in BarContainer
that would subset the vector of Bar
to match with the expression given as argument.
Bar = R6::R6Class(
### Class name
"Bar",
### Public members
public = list(
### Attributes
a = -1,
b = -1,
c = -1,
### Initialize
initialize = function(A, B, C)
{
self$a = A
self$b = B
self$c = C
}
)
)
BarContainer = R6::R6Class(
### Class name
"BarContainer",
### Public members
public = list(
### Attributes
self$bars = c(),
### Initialize
initialize = function(input)
{
bars = input
}#,
### Subset
subset = function(expr)
{
# ??? self[eval(parse(text=expr))] ???
}
)
)
Here is a possible use case for subset
mySubsettedContainer = myContainer$subset(a == 3 && (b > 0 || c > 0) )
How could this be implemented? Any other solution (eventually using several expressions or something else) to implement this subset
method?
Note that Bar
has a whole bunch of helpful methods and this oop approach really clean things up. I would like to avoid keep my Bar
objects as rows of a data.frame
(e.g. bars = data.frame(a = .., b = .., c = ..)
) even if it would make the implementation of a subset
method in BarContainer
much easier.
If you want BarContainer$subset
to work in this way, you will need to capture the expression and evaluate it for each Bar
in BarContainer
to get a logical vector, then use this to subset the bars
field in your container. The resulting list of Bar
objects can then be passed to a BarContainer$new
call to return a new BarContainer
with the subsetted Bar
objects.
You can capture the expression with match.call()
and evaluate it in the context of each Bar
using an lapply
, though you need to represent each Bar
as a list within the lapply
for it to be a valid evaluation context. That means the implementation would look something like this:
BarContainer = R6::R6Class(
### Class name
"BarContainer",
### Public members
public = list(
### Attributes
bars = c(),
### Initialize
initialize = function(input)
{
self$bars = input
},
### Subset
subset = function(expr)
{
ex <- as.list(match.call()[-1])$exp
ss <- sapply(self$bars, function(x){
eval(ex, envir = as.list(x), enclos = parent.frame())})
return(BarContainer$new(self$bars[ss]))
}
)
)
So we can create an example set-up like this:
# Create 4 Bars for our container:
bar1 <- Bar$new(1, 1, 1)
bar2 <- Bar$new(2, 2, 2)
bar3 <- Bar$new(3, 3, 3)
bar4 <- Bar$new(4, 4, 4)
# Populate our container:
bar_container <- BarContainer$new(c(bar1, bar2, bar3, bar4))
And our subset function works as expected:
subsetted_container <- bar_container$subset(a > 1 & c < 4)
subsetted_container$bars
#> [[1]]
#> <Bar>
#> Public:
#> a: 2
#> b: 2
#> c: 2
#> clone: function (deep = FALSE)
#> initialize: function (A, B, C)
#>
#> [[2]]
#> <Bar>
#> Public:
#> a: 3
#> b: 3
#> c: 3
#> clone: function (deep = FALSE)
#> initialize: function (A, B, C)
Remember, the subsetted BarContainer
won't hold copies of the original Bar
objects, but pointers to the objects themselves. So if you alter the first Bar
within the subsetted container, it will alter bar2
and the second Bar
in the non-subsetted container. I'm guessing this is the desired behaviour anyway, but if not you will have to deep clone each Bar
as part of your subsetting method.