R R6 Classes
R6 is a modern object-oriented system for R that brings features familiar from Python, Java, and other languages — mutable objects with public and private members, method chaining, and active bindings. R6 objects modify themselves in place rather than returning modified copies, which makes them efficient for stateful systems.
R6 vs S3 vs S4
Feature S3 S4 R6 ───────────────────────────────────────────────────────────────── Mutable state No No Yes (reference semantics) Private fields No No Yes Method chaining No No Yes (with invisible(self)) Encapsulation Minimal Partial Full Ease of use Easy Complex Moderate
Installing and Loading R6
install.packages("R6")
library(R6)
Creating an R6 Class
library(R6)
Counter <- R6Class("Counter",
public = list(
count = 0, # public field
initialize = function(start=0) {
self$count <- start
},
increment = function(by=1) {
self$count <- self$count + by
invisible(self) # enables method chaining
},
reset = function() {
self$count <- 0
invisible(self)
},
print = function(...) {
cat("Counter:", self$count, "\n")
invisible(self)
}
)
)
c1 <- Counter$new(10)
c1$print() # Counter: 10
c1$increment(5)
c1$print() # Counter: 15
# Method chaining
c1$increment(3)$increment(2)$print()
# Counter: 20
Private Fields and Methods
BankAccount <- R6Class("BankAccount",
private = list(
balance = 0, # not accessible from outside
log_transaction = function(type, amount) {
cat("[LOG]", type, "₹", amount, "| Balance: ₹", private$balance, "\n")
}
),
public = list(
owner = NULL,
initialize = function(owner, initial_balance=0) {
self$owner <- owner
private$balance <- initial_balance
},
deposit = function(amount) {
if (amount <= 0) stop("Amount must be positive")
private$balance <- private$balance + amount
private$log_transaction("DEPOSIT", amount)
invisible(self)
},
withdraw = function(amount) {
if (amount > private$balance) stop("Insufficient funds")
private$balance <- private$balance - amount
private$log_transaction("WITHDRAW", amount)
invisible(self)
},
get_balance = function() private$balance,
print = function(...) {
cat("Account Owner:", self$owner, "\n")
cat("Balance: ₹", private$balance, "\n")
invisible(self)
}
)
)
acc <- BankAccount$new("Asha", 10000)
acc$deposit(5000)$withdraw(2000)
acc$get_balance() # 13000
acc$print()
Active Bindings — Computed Properties
Circle <- R6Class("Circle",
private = list(r = 0),
active = list(
radius = function(value) {
if (missing(value)) return(private$r)
if (value < 0) stop("Radius must be non-negative")
private$r <- value
},
area = function() pi * private$r^2, # read-only
circumference = function() 2 * pi * private$r # read-only
)
)
c1 <- Circle$new()
c1$radius <- 7
c1$area # 153.94
c1$circumference # 43.98
Inheritance
SavingsAccount <- R6Class("SavingsAccount",
inherit = BankAccount,
private = list(rate = 0.06),
public = list(
initialize = function(owner, balance, rate=0.06) {
super$initialize(owner, balance)
private$rate <- rate
},
add_interest = function() {
interest <- self$get_balance() * private$rate
self$deposit(interest)
cat("Interest added: ₹", interest, "\n")
invisible(self)
}
)
)
sav <- SavingsAccount$new("Balu", 20000)
sav$add_interest()
sav$get_balance() # 21200
Cloning R6 Objects
# R6 objects are references — assignment does NOT copy acc2 <- acc # both point to the SAME object! acc2$deposit(1000) # modifies acc too! # To get an independent copy: acc3 <- acc$clone() acc3$deposit(500) # only acc3 is modified
R6 is the right choice when you need objects that maintain state over time — simulation systems, GUI components, database connections, or any object that changes itself through method calls. Its familiar class structure makes it easy to apply if you already know OOP from another language.
