Skip to Content
Course content

124: R6 Classes for Reference Semantics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Think about the difference between sending someone a Word document via email and sharing a Google Doc with them. If I email you a .docx file, you have your own copy. You can delete paragraphs, change the font to Comic Sans, or rewrite the ending, and my original file remains untouched. If you want me to see those changes, you have to send the whole file back to me, and I have to overwrite my version with yours. That's how almost everything in R works—it's called "value semantics" or "copy-on-modify."

Now, imagine a shared Google Doc. We both have a link to the exact same document. If you change a sentence, I see it happen in real-time. We aren't passing copies back and forth; we are both referencing the same object in memory. This is "reference semantics," and in R, the R6 package is our primary tool for achieving this.

Moving Beyond the Copy-on-Modify Headache

In standard R (S3 or S4), if you have a large object and you want to change one small piece of it inside a function, R often creates a copy of that entire object. When your data grows to millions of rows, this becomes a performance nightmare. Even for small objects, the syntax gets clunky: you're constantly doing my_obj <- update_function(my_obj).

With R6, we stop doing that. We create an object that stays put in memory, and we tell R to go to that specific address and change a value. I find this indispensable when I'm building complex systems—like a database connection manager or a simulation engine—where I need a single "source of truth" that different parts of my program can update simultaneously.

Building a State-Aware Bank Account

Let's look at how this actually looks in code. We'll build a simple bank account. In a traditional R function, adding money would require returning a new version of the account. Here, we just update the internal state.

library(R6)

BankAccount <- R6Class("BankAccount",
  public = list(
    owner = NULL,
    balance = 0,
    
    initialize = function(owner, initial_deposit) {
      self$owner <- owner
      self$balance <- initial_deposit
    },
    
    deposit = function(amount) {
      self$balance <- self$balance + amount
      invisible(self) # Returning 'self' allows for method chaining
    },
    
    withdraw = function(amount) {
      if (amount > self$balance) {
        stop("Insufficient funds!")
      }
      self$balance <- self$balance - amount
      invisible(self)
    }
  )
)

# Let's create an account
my_account <- BankAccount$new("Alice", 100)

Notice the self$ syntax. In R6, self is the equivalent of this in Java or Python. It tells R, "I'm talking about the field belonging to this specific instance of the class."

Proving the Reference

This is the part where the "Google Doc" analogy really clicks. Watch what happens when we assign our account to a second variable. In standard R, this would create a copy. In R6, it just creates another pointer to the same object.

# Create a second reference to the same account
bob_reference <- my_account

# Bob deposits money into his reference
bob_reference$deposit(50)

# Check Alice's account balance
print(my_account$balance) 
# Output: 150

Alice's balance changed even though we called the method on bob_reference. Why? Because my_account and bob_reference are just two different names for the same piece of memory. I've seen a lot of developers get tripped up by this when they expect a copy, so be careful: if you actually want a separate copy of an R6 object, you have to explicitly call my_account$clone().




📋 Practical Task

Implementing a Persistent Portfolio Tracker

Your task is to create an R6 class called PortfolioTracker that manages a collection of stock symbols and their quantities. Unlike a simple list, this tracker needs to maintain its state across different variables.

Requirements:

  • The class should have a public field called holdings, initialized as an empty named list.
  • Implement a method add_stock(symbol, quantity). If the symbol already exists in holdings, add the quantity to the existing amount. If it doesn't exist, create a new entry.
  • Implement a method get_total_shares() that returns the sum of all quantities in the portfolio.
  • Demonstrate reference semantics: Create one portfolio object, assign it to a second variable, use the second variable to add stocks, and then print the holdings of the first variable to prove they are the same object.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.