0

I'm working on a project in which I need complex S4 objects with multiple layers of list. I'm wondering if this is a way to modify the @ function so that I can access data at the most granular level in an elegant way. To illustrate my problem, I created a sample S4 object below and my goal is to modify the @ function so I can achieve below:

jack <- new("Person", name = "Jack", other_info = list(age = 30, gender = "male")
jack@age 
# 30

I have tried many other ways such as creating a generic function like %@% but dealing with operator precedence is extremely hard.

Alan Wang
  • 1
  • 1
  • Without suitable `setClass` your example will produce an error. Please make your example reproducible. I'm also confused what you're trying to do. `@` extracts/replaces the slots of the object. If you want to access specific sub-elements of specific slots, why not define a suitable accessor method? – Maurits Evers Oct 30 '19 at 02:53
  • related question: https://stackoverflow.com/questions/52297071/how-to-overload-s4-slot-selector-to-be-a-generic-function/52328039#52328039 – JDL Oct 30 '19 at 14:09

2 Answers2

0

Further to my comment above, I think there is a misunderstanding about what @ does in the context of S4 objects.

Let's create a new Person class

setClass(
    "Person",
    representation(
        name = "character",
        other_info = "list"))

and define an instance as per your example

jack <- new("Person", name = "Jack", other_info = list(age = 30, gender = "male"))

Notice that jack has two slots, which can be accessed with the @ operator; we have jack@name (which can also be accessed with slot(jack, "name")) and jack@other_info (or slot(jack, "other_info")).

To access specific data in specific slots, the kinda-sorta OOP-way (see @HongOoi's comment) would be to define an accessor method

setGeneric("get_age", function(object) standardGeneric("get_age"))
setMethod("get_age", signature(object = "Person"), function(object) object@other_info$age)

Then we can do

get_age(jack)
#[1] 30
Maurits Evers
  • 49,617
  • 4
  • 47
  • 68
  • Well, the _S4_ kinda-sorta-OOP way would be to do `get_age(jack)`. The way in most every other OOP language would be to make the method part of the class itself, as in `jack.get_age()`. Some will even let you omit the `get_age` boilerplate: `jack.age`. You can do the former with refclasses and R6, but (still) not the latter. – Hong Ooi Oct 30 '19 at 04:06
  • ^^ I like the kinda-sorta part & made an edit. Thanks @HongOoi and I agree. But in S4, the methods are at least directly *associated* with the class itself through the `signature`. – Maurits Evers Oct 30 '19 at 04:10
  • I just realised you can actually do what the OP wants, using R6 – Hong Ooi Oct 30 '19 at 04:12
0

If you use R6 classes, you can actually do something like this with active bindings.

Person <- R6::R6Class("Person",

public=list(
    name=NULL,
    initialize=function(name, age, gender)
    {
        self$name <- name
        private$other_info$age <- age
        private$other_info$gender <- gender
    }
),

active=list(
    age=function()
    {
        private$other_info$age
    },
    gender=function()
    {
        private$other_info$gender
    }
),

private=list(
    other_info=list(
        age=NULL,
        gender=NULL
    )
))

jack <- Person$new("jack", 42, "male")

jack$age
# [1] 42
Hong Ooi
  • 56,353
  • 13
  • 134
  • 187