Изменить объект S3, не возвращая его?
Я новичок в объектно-ориентированном программировании на R и борюсь с тем, как правильно написать функцию, которая модифицирует объект.
Этот пример работает:
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)
Но я предполагаю, что должен быть более чистый способ сделать это, не возвращая весь объект:
addApples(store1, 5) # Preferable line...
store1 <- addApples(store1, 5) # ...instead of this line
Как правильно писать модифицирующие функции в R? "<< -"?
Обновление: Спасибо всем за то, что стал Rosetta Stone для ООП в R. Очень информативно. Проблема, которую я пытаюсь решить, очень сложна с точки зрения последовательности действий, поэтому жесткость эталонных классов может помочь структуре. Я хотел бы принять все ответы в качестве ответов, а не только один.
4 ответа
Вот реализация эталонного класса, как предлагается в одном из комментариев. Основная идея заключается в создании ссылочного класса Stores
это имеет три поля: apples
, pears
а также fruits
(отредактировано, чтобы быть методом доступа). initialize
метод используется для инициализации нового магазина, addApples
метод добавляет яблоки в магазин, в то время как show
метод эквивалентен print
для других объектов.
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")
}
)
)
Если мы инициализируем новый магазин и называем его, вот что мы получаем
FruitStore = Stores$new(apples = 3, pears = 4)
FruitStore
# 3 apples and 4 pears
Теперь, ссылаясь на addApples
метод, давайте добавим 4 яблока в магазин
FruitStore$addApples(4)
FruitStore
# 7 apples and 4 pears
РЕДАКТИРОВАТЬ. Согласно предложению Хэдли, я обновил свой ответ так, чтобы fruits
теперь метод доступа. Это остается обновленным, поскольку мы добавляем больше apples
в магазин. Спасибо, Хэдли.
На самом деле вы можете сделать это с классами S3 с функциями замены, если вы хотите сэкономить, погружаясь в справочные классы. Во-первых, ваш пример
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."
Обратите внимание, как с помощью NextMethod
значит я не должен делать print(store1)
Я могу просто напечатать store
, В основном, как только я переназначу x
чтобы быть тем, что я хочу показать на экране, я просто отправляю по умолчанию print
метод. Затем:
`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."
Тада! Опять же, не совсем типичный случай использования S3. За исключением [
а также [[
функции, функции замены, как правило, предназначены для обновления атрибутов (например, класса, длины и т. д.), но я не вижу слишком большого вреда в его расширении.
Обратите внимание, что это не реальное назначение ссылки. На самом деле, ваш fruitstore
Объект копируется, изменяется и повторно назначается исходной переменной (см. R Docs).
Вы должны взглянуть на пакет data.table.
Загрузите пакет: library(data.table)
Определите объект data.table:
store1 <- data.table(apples=3,
pears=4,
fruits=7)
Затем определите функцию addApple:
addApple <- function(data,i) {data[,apples:=(data[1,apples]+i)];
data[,fruits:=(data[1,apples]+data[1,pears])]}
Вот и все.
Когда ты пишешь addApple(store1)
Вы должны получить + я яблоки и фрукты "яблоки + груши".
И вы все равно можете определить свой класс S3, если хотите, просто убедитесь, что он наследует классы data.frame и data.table.
class(store1) <- c("fruitstore",class(store1))
Еще одна вещь, вы должны переписать свой метод S3 для печати, сделав print явным:
print.fruitstore <- function(x) {
print(paste(x$apples, "apples and", x$pears, "pears", sep=" "))
}
Или вы можете просто использовать cat:
print.fruitstore <- function(x) {cat(x$apples, "apples and", x$pears, "pears")}
Вот реализация, использующая пакет proto, который объединяет объекты и классы в единую концепцию прототипов. Например, здесь действительно нет разницы между Fruitstore
который является объектом, который играет роль класса и store1
который является объектом, который играет роль конкретного магазина. Они оба прото-объекты.
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()