问题
I'm new to object-oriented programming in R and struggle with how to properly write a function that modifies an object.
This example works:
store1 <- list(
apples=3,
pears=4,
fruits=7
)
class(store1) <- "fruitstore"
print.fruitstore <- function(x) {
paste(x$apples, "apples and", x$pears, "pears", sep=" ")
}
print(store1)
addApples <- function(x, i) {
x$apples <- x$apples + i
x$fruits <- x$apples + x$pears
return(x)
}
store1 <- addApples(store1, 5)
print(store1)
But I suppose there should be a cleaner way to do this without returning the whole object:
addApples(store1, 5) # Preferable line...
store1 <- addApples(store1, 5) # ...instead of this line
What is the proper way to write modify-functions in R? "<<-"?
Update: Thank you all for what became a Rosetta Stone for OOP in R. Very informative. The problem I'm trying to solve is very complex in terms of flow, so the rigidness of reference classes may bring the structure to help. I wish I could accept all responses as answers and not only one.
回答1:
Here is a reference class implementation, as suggested in one of the comments. The basic idea is to set up a reference class called Stores
that has three fields: apples
, pears
and fruits
(edited to be an accessor method). The initialize
method is used to initialize a new store, the addApples
method adds apples to the store, while the show
method is equivalent to print
for other objects.
Stores = setRefClass("Stores",
fields = list(
apples = "numeric",
pears = "numeric",
fruits = function(){apples + pears}
),
methods = list(
initialize = function(apples, pears){
apples <<- apples
pears <<- pears
},
addApples = function(i){
apples <<- apples + i
},
show = function(){
cat(apples, "apples and", pears, "pears")
}
)
)
If we initialize a new store and call it, here is what we get
FruitStore = Stores$new(apples = 3, pears = 4)
FruitStore
# 3 apples and 4 pears
Now, invoking the addApples
method, let us add 4 apples to the store
FruitStore$addApples(4)
FruitStore
# 7 apples and 4 pears
EDIT. As per Hadley's suggestion, I have updated my answer so that fruits
is now an accessor method. It remains updated as we add more apples
to the store. Thanks @hadley.
回答2:
You can actually do this with S3 classes with replacement functions if you want to save yourself diving into reference classes. First, your example
store1 <- list(apples=3,pears=4)
class(store1) <- "fruitstore"
print.fruitstore <- function(x) {
x <- paste(unlist(store1), names(store1), collapse=", ")
x <- paste0(x, " for a total of ", sum(unlist(store1)), " fruit.")
NextMethod()
}
store1
# [1] "3 apples, 4 pears for a total of 7 fruit."
Notice how using NextMethod
means I don't have to do print(store1)
, I can just type store
. Basically, once I re-assign x
to be what I want to show up on screen, I just dispatch the default print
method. Then:
`addapples<-` <- function(x, ...) UseMethod("addapples<-")
`addapples<-.fruitstore` <- function(x, value) {
x[["apples"]] <- x[["apples"]] + value
x
}
addapples(store1) <- 4
store1
# [1] "7 apples, 4 pears for a total of 11 fruit."
Tada! Again, not really the typical S3 usage case. Except for the [
and [[
functions, replacement functions are typically intended to update attributes (e.g. class, length, etc.), but I don't see too much harm in stretching that.
Note this is not a real by reference assignment. Really, your fruitstore
object is copied, modified, and re-assigned to the original variable (see R Docs).
回答3:
Here is an implementation using the proto package which unifies objects and classes into the single concept of prototypes. For example, here there is really no difference between Fruitstore
which is an object that plays the role of a class and store1
which is an object that plays the role of a particular store. They are both proto objects.
library(proto)
Fruitstore <- proto(
addApples = function(., i) {
.$apples <- .$apples + i
.$fruits <- .$apples + .$pears
},
print = function(.) cat(.$apples, "apples and", .$pears, "pears\n")
)
# define store1 as a child of Fruitstore
store1 <- Fruitstore$proto(apples = 3, pears = 4, fruits = 7)
store1$addApples(5)
store1$print()
回答4:
You should take a look at the data.table package.
Load the package: library(data.table)
Define a data.table object:
store1 <- data.table(apples=3,
pears=4,
fruits=7)
Then define the function addApple:
addApple <- function(data,i) {data[,apples:=(data[1,apples]+i)];
data[,fruits:=(data[1,apples]+data[1,pears])]}
That's it.
When you write addApple(store1)
you should get +i apples and "apples + pears" fruits.
And you can still define your S3 class if you want, just make sure that it inherits the data.frame and data.table classes.
class(store1) <- c("fruitstore",class(store1))
One more thing, you should rewrite your S3 method for print by making print explicit:
print.fruitstore <- function(x) {
print(paste(x$apples, "apples and", x$pears, "pears", sep=" "))
}
Or you could just use cat:
print.fruitstore <- function(x) {cat(x$apples, "apples and", x$pears, "pears")}
来源:https://stackoverflow.com/questions/21243359/modify-s3-object-without-returning-it