Is It Bad Practice to Access S4 Objects Slots Directly Using @

Is it bad practice to access S4 objects slots directly using @?

In general it is good programming practice to separate the content of an object from the interface, see this wikipedia article. The idea is to have the interface separate from the implementation, in that way the implementation can change considerably, without affecting any of the code that interfaces with that code, e.g. your script. Therefore, using @ creates less robust code that is less likely to work in a few years time. For example in the sp-package mentioned by @mdsummer the implementation of how polygons are stored might change because of speed or progressing knowledge. Using @, your code breaks down, using the interface your code works still. Except ofcourse if the interface also changes. But changes to the implementation are much more likely than interface changes.

Accessing and modifying arbitrarily deep nested S4 slots

Still happy to hear if anyone has thoughts. In the meantime, I've been unable to find a way to dynamically access and modify deep recursive layers in an S4 object. Rather, the best solution was to recursively collapse the object into a list of layers.

Due to the memory overhead of collapsing layers in a class-specific as.list operation, I've decided to go for a list of objects while enforcing relationships between consecutive layers.

Accessing slots of an S4 function superclass

Resorting to a little language manipulation

setClass("Pow", representation("function", pow="numeric"),
prototype=prototype(
function(x) {
self <- eval(match.call()[[1]])
x^self@pow
}, pow=2))

and then

> f = g = new("Pow")
> g@pow = 3
> f(2)
[1] 4
> g(2)
[1] 8

although as Spacedman says things can go wrong

> f <- function() { Sys.sleep(2); new("Pow") }
> system.time(f()(2))
user system elapsed
0.002 0.000 4.005

A little more within the lines but deviating from the problem specification and probably no less easy on the eyes is

setClass("ParameterizedFunFactory",
representation(fun="function", param="numeric"),
prototype=prototype(
fun=function(x, param) function(x) x^param,
param=2))

setGeneric("fun", function(x) standardGeneric("fun"))
setMethod(fun, "ParameterizedFunFactory",
function(x) x@fun(x, x@param))

with

> f = g = new("ParameterizedFunFactory")
> g@param = 3
> fun(f)(2)
[1] 4
> fun(g)(2)
[1] 8

R S4 slots as lists of custom classes

For those looking for an answer to this. No, there is no way to restrict a list in S4 to have elements of certain type. This actually makes sense, since lists in R are designed to contain any type of element, so why lists on S4 should be distinct?

slot assignment: `@` vs `slot()` vs `setReplaceMethod()`

By definition, "not having to fuss with the details of the S4 class structure" means end-users should not have to know about your slots. As such, any wrappers you write as in steps 2 and 3 are more for internal consistency checks. I think more important is to check for edge cases where you think your integrity checks would fail using unit tests.

As you have pointed out, #1 can be ruled out fairly easily, and should only be used in internal methods. Whether you encourage #2 or implement #3 depends on the contents of the variable and personal taste, but I would encourage the latter. For example, if you have a logical flag, you can use #2, but more descriptive would be enableFoo(). That being said, if you have to consider developer time (which in real life is almost always true) you should think briefly about the trade-off between relegating mutators to slot<- for members that probably won't be accessed frequently (e.g., by less than 1% of users), versus implementing custom mutators for everything as in #3.

Finally, since almost all three of R's OOP systems are essentially syntactic sugar and aren't really respected semantically by the language (where only S4 can claim some exception), it is easy to forget the fundamental ideas behind object-oriented programming as implemented in other languages: any code executing outside of your methods should not know about the object's members. You are providing an external interface to the world that is and should be a black box.



Related Topics



Leave a reply



Submit