Active Bindings in R: makeActiveBinding() for Computed Variables

An active binding is a variable that runs a function every time you read or write to it. Instead of storing a static value, it computes a result on access — like a property getter in object-oriented languages. Create them with makeActiveBinding().

What Are Active Bindings?

Normal variables store a value. Active bindings run a function:

# Normal variable: stores a value x <- 42 cat("x:", x, "\n") # Just returns 42 # Active binding: runs a function each time you access it makeActiveBinding("now", function() Sys.time(), globalenv()) cat("now:", format(now), "\n") Sys.sleep(0.1) cat("now:", format(now), "\n") # Different value each time! # Clean up rm(now)


  
# Active binding as a counter env <- new.env(parent = emptyenv()) env$.count <- 0 makeActiveBinding("counter", function() { env$.count <- env$.count + 1 env$.count }, globalenv()) cat("First access:", counter, "\n") # 1 cat("Second access:", counter, "\n") # 2 cat("Third access:", counter, "\n") # 3 rm(counter)


  

makeActiveBinding() Syntax

# makeActiveBinding(name, function, environment) # Read-only computed property data_env <- new.env(parent = emptyenv()) data_env$values <- c(10, 20, 30, 40, 50) makeActiveBinding("data_mean", function() { mean(data_env$values) }, globalenv()) cat("Mean:", data_mean, "\n") # Change the underlying data data_env$values <- c(100, 200, 300) cat("Updated mean:", data_mean, "\n") # Automatically reflects new data rm(data_mean)


  

Read-Write Active Bindings

If the function accepts an argument, you can set the binding's value:

env <- new.env(parent = emptyenv()) env$.temp_celsius <- 0 makeActiveBinding("temp_fahrenheit", function(value) { if (missing(value)) { # Getter: convert C to F env$.temp_celsius * 9/5 + 32 } else { # Setter: convert F to C and store env$.temp_celsius <- (value - 32) * 5/9 } }, globalenv()) env$.temp_celsius <- 100 cat("100C in F:", temp_fahrenheit, "\n") temp_fahrenheit <- 32 cat("32F in C:", env$.temp_celsius, "\n") rm(temp_fahrenheit)


  

Practical Uses

Validated Assignment

env <- new.env(parent = emptyenv()) env$.age <- 25 makeActiveBinding("age", function(value) { if (missing(value)) return(env$.age) if (!is.numeric(value) || value < 0 || value > 150) { stop("age must be a number between 0 and 150") } env$.age <- value }, globalenv()) cat("Age:", age, "\n") age <- 30 cat("Updated age:", age, "\n") # This would error: tryCatch( age <- -5, error = function(e) cat("Error:", conditionMessage(e), "\n") ) cat("Age after invalid assignment:", age, "\n") # Still 30 rm(age)


  

Logging Access

env <- new.env(parent = emptyenv()) env$.secret <- "classified" env$.access_log <- character() makeActiveBinding("secret", function(value) { if (missing(value)) { entry <- paste("READ at", format(Sys.time())) env$.access_log <- c(env$.access_log, entry) return(env$.secret) } entry <- paste("WRITE at", format(Sys.time())) env$.access_log <- c(env$.access_log, entry) env$.secret <- value }, globalenv()) cat("Secret:", secret, "\n") secret <- "new_value" cat("Updated:", secret, "\n") cat("\nAccess log:\n") for (entry in env$.access_log) cat(" ", entry, "\n") rm(secret)


  

Practice Exercise

# Exercise: Create an active binding called "random_greeting" # that returns a random greeting each time it's accessed. # Greetings: "Hello!", "Hi there!", "Hey!", "Greetings!", "Howdy!" # Write your code below:


  
Click to reveal solution ```r
makeActiveBinding("random_greeting", function() { greetings <- c("Hello!", "Hi there!", "Hey!", "Greetings!", "Howdy!") sample(greetings, 1) }, globalenv()) cat(random_greeting, "\n") cat(random_greeting, "\n") cat(random_greeting, "\n") cat(random_greeting, "\n") rm(random_greeting)

  
**Explanation:** Each time `random_greeting` is accessed, the function runs and `sample()` picks a random element. No two accesses are guaranteed to return the same value.

Summary

Feature Syntax Purpose
Create binding makeActiveBinding("name", fn, env) Define computed variable
Read-only function() { ... } (no args) Computed property
Read-write function(value) { ... } Validated getter/setter
Check if active bindingIsActive("name", env) Test binding type
R6 active fields active = list(name = function() ...) OOP computed properties

FAQ

How are active bindings used in R6 classes?

R6 classes have an active field in their class definition. Active fields look like regular fields to the user but run functions underneath — perfect for computed properties, validation, and lazy initialization.

Do active bindings have performance overhead?

Yes — each access runs a function call instead of a simple value lookup. For rarely-accessed properties this is negligible. For hot loops accessing millions of times, use regular variables instead.

What's Next?

  1. R6 Classes — active bindings are a key feature of R6's OOP system
  2. R Environments — active bindings live in environments
  3. R Closures — the function in an active binding is often a closure