Andrei
Andrei

Reputation: 2665

Is it possible to have dynamic binding in R?

Is it possible to have a variable with a dynamic binding in R? Say, to have something like this to work:

f1a <- function() {
  x <- 1
  f2()
}

f1b <- function() {
  x <- 2
  f2()
}

f2 <- function() {
  f3()
}

f3 <- function() {
  # print x
}

f1a() # prints 1
f1b() # prints 2

I can pass x down the stack explicitly, but that is not what I'm looking for. I'm asking about a possibility for a dynamic binding. I understand that many people would say passing x explicitly is always preferable, but suppose I cannot change the definition of f2 (I can change f1a, f1b and f3). I can have a global variable, of course, but I'd rather use a dynamic binding if possible.

Upvotes: 1

Views: 150

Answers (1)

Dylan Russell
Dylan Russell

Reputation: 1108

f1a <- function() {
  x <- 1
  f2()
}

f1b <- function() {
  x <- 2
  f2()
}

f2 <- function() {
  f3()
}

f3 <- function() {
  get("x", envir = parent.frame(2))
}

Simply move the environment in which f3 is looking for the variable x.

> f1a()
[1] 1
> f1b()
[1] 2

get("x", envir = parent.frame(2)) looks for a variable with symbol x in an environment two frames up the call stack:

f3 <- function() {
  get("x", envir = parent.frame(2))
  rlang::trace_back()
}

> f1a()
    █
 1. └─global::f1a()
 2.   └─global::f2()
 3.     └─global::f3()

If you need to iterate up an entire call stack until the first of a symbol is encountered, simply use dynGet():

f1a <- function() {
  x <- 1
  f2()
}

f1b <- function() {
  x <- 2
  f2()
}

f2 <- function() {
  f3()
}

f3 <- function() {
  f4()
}

f4 <- function() {
  f5()
}

f5 <- function() {
  dynGet("x")
}

> f1a()
[1] 1
> f1b()
[1] 2

This will stop at the first x encountered moving up the call stack from the calling environment. So if we adjust f2:

f2 <- function() {
  x <- 3
  f3()
}

> f1a()
[1] 3
> f1b()
[1] 3

Again, the full call stack can be seen with rlang::trace_back:

f5 <- function() {
  dynGet("x")
  rlang::trace_back()
}

> f1a()
    █
 1. └─global::f1a()
 2.   └─global::f2()
 3.     └─global::f3()
 4.       └─global::f4()
 5.         └─global::f5()

Upvotes: 5

Related Questions