The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Creating and using super variables with newSuperVar() in the quickcode R package

Obinna (OBI) Obianom

2026-08-03

What is a super variable

A regular R variable lives in whatever environment created it. Pass it into a function, and the function only sees a copy; change that copy inside the function, and the original outside is untouched. Assign inside a for loop or an lapply() call and, again, only the local copy changes. This is normal, sensible R scoping — but it also means that sharing genuinely mutable state across scopes (functions, loops, apply-family calls, nested environments) usually forces you to reach for <<-, explicit environments, or reference-class objects, all of which add ceremony to what is conceptually a simple idea: “I want one variable that stays the same object no matter where I touch it from.”

newSuperVar() creates exactly that: a super variable — a named object that is attached to the search path so it is visible and mutable from any scope in the current R session, with optional protections layered on top: type/class enforcement, an explicit lock, and a cap on how many times its value may be changed.

Creating a super variable


# Task: create a super variable to store a dataset that should not be altered
newSuperVar(mtdf, value = austres) # create a super variable named mtdf
head(mtdf) # view it like any other object

mtdf.class # view the stored class of the variable, e.g. "ts"
# once created, any new value assigned to mtdf via mtdf.set() MUST have this same class

newSuperVar(variable, value = 0L, lock = FALSE, editn = NULL) takes the (unquoted) name you want the super variable to have, the initial value, whether it should be locked, and editn, an optional cap on the number of times it may later be changed.

Two things happen the moment you call it:

  1. The object mtdf becomes available by that name from anywhere in your R session — not just the environment where you called newSuperVar().
  2. A family of companion functions and an attribute, all prefixed with mtdf., are attached alongside it (mtdf.set(), mtdf.rm(), mtdf.contains(), mtdf.round(), mtdf.signif(), mtdf.class, plus mtdf.push()/mtdf.pop(), and — for data frames specifically — mtdf.head()/mtdf.tail()).

Trying to declare a super variable under a name that already exists produces an error telling you to remove the existing one first (see .rm() below) rather than silently overwriting it.

The attached methods

.set() — updating the value

Ordinary reassignment (mtdf <- something_else) will not change the super variable itself; it just creates a local variable that happens to shadow the name in your current scope. To actually update the stored value, use .set():


newSuperVar(edtvec, value = number(5))
edtvec # view content of the vector

# edtvec.set(letters) # ERROR: Cannot set to a value with a different class
#                      # than the original value (numeric here, letters is character)

edtvec.set(number(20)) # set to new numbers
edtvec # view updated output

Because .set() is a function, not a <- assignment, it works identically no matter which scope it is called from:


for (pu in 1:8) {
  print(edtvec)          # view output within the loop
  edtvec.set(number(pu)) # set to new numbers within the for loop
}

lc <- lapply(1:8, function(pu) {
  print(edtvec)           # view output within the loop
  edtvec.set(number(pu))  # set to new numbers within the lapply loop
})

# compare that to an ordinary local variable, which lapply() cannot mutate:
bim <- 198
lc <- lapply(1:8, function(j) {
  print(bim)
  bim <- j # only changes the local copy inside this call; outer bim is untouched
})

Type safety is automatic

Every call to .set() checks the class of the new value against the class the super variable was created with, and refuses the update (with an informative error) if they don’t match. This is what makes super variables safer to share across a large script than a plain global variable: a typo that assigns the wrong kind of value is caught immediately instead of silently corrupting downstream logic.

.round() and .signif() — numeric rounding in place

For numeric or data-frame-of-numeric super variables, .round(digits) and .signif(digits) update the stored value in place, rounding to the given number of decimal places or significant digits respectively:


newSuperVar(mtdf3, value = beaver1, lock = TRUE)
head(mtdf3) # view original values

mtdf3.round(1) # round to 1 decimal place
head(mtdf3) # view rounded values

mtdf3.signif(2) # round to 2 significant digits
head(mtdf3) # view again

.contains() — searching the value

.contains() checks whether a pattern or value is present anywhere in the super variable, using the same matching options as grep() (ignore.case, perl, fixed, useBytes, invert). For vectors it searches the vector directly; for a data frame, matrix, or array it searches within a specific column, named via the df.col argument:


# Task: create and search a data frame
newSuperVar(lon2, value = mtcars) # declares lon2
lon2 # view content of lon2

lon2.contains("21.0", df.col = "mpg") # search the mpg column
                                        # for the character "21.0"
lon2.contains(21.0, df.col = "mpg")    # search the mpg column
                                        # for the numeric value 21.0

# remove lon2 as a super variable once done with it
exists("lon2") # TRUE, before removal
lon2.rm()
exists("lon2") # FALSE, after removal

# Task: create and search a vector
newSuperVar(lon3, value = number(10, seed = 12)) # declares lon3
lon3 # view content of lon3

lon3.contains(72)               # TRUE/FALSE - does the vector contain 72?
lon3.contains(72, fixed = TRUE) # same search, using fixed (non-regex) matching

lon3.rm() # remove lon3 as a super variable

.push() and .pop() — building a new value from the current one

.push(add, .df = c("row", "col")) and .pop(n = 10, .df = c("row", "col")) compute what the super variable’s vector, list, or data frame would look like with an element added or removed (for data frames, a row or column, controlled by .df), and return that result — they do not update the stored value on their own. To actually persist the change, feed the result back into .set():


newSuperVar(queue, value = c(1, 2, 3))

queue.push(4)          # returns c(1, 2, 3, 4), queue itself is still c(1, 2, 3)
queue.set(queue.push(4)) # now queue is actually updated to c(1, 2, 3, 4)

queue.set(queue.pop(1))  # drop the last element and persist the change

.head() and .tail() — previewing data frames

When a super variable’s initial value is a data frame, two extra helpers are attached automatically: .head(n = 10) and .tail(n = 10), printing the first or last n rows. They are not created for non-data-frame super variables.

.rm() — removing a super variable

.rm() detaches the super variable and all of its companion functions, freeing up the name for reuse (including re-declaring it with newSuperVar() again):


exists("lon3") # TRUE
lon3.rm()
exists("lon3") # FALSE

Locking a super variable

Passing lock = TRUE at creation time adds an extra layer of protection around the stored binding, intended for values you want to guard more strictly against being changed by anything other than the super variable’s own methods:


newSuperVar(mtdf3, value = beaver1, lock = TRUE)

Locked or not, updates always go through .set(), .round(), or .signif() — those methods know how to handle the lock internally, so day-to-day usage looks identical whether lock is TRUE or FALSE. Use lock = TRUE for values you want to flag as intentionally protected, such as reference datasets or configuration you don’t want casually reassigned elsewhere in a long script.

Limiting how many times a value can change

editn caps the number of successful .set() calls a super variable will accept:


# Task: create a super variable that can only be edited 3 times
newSuperVar(man1, value = number(5), editn = 3)
man1 # view value

man1.set(number(10)) # change value - 1st edit, succeeds
man1

man1.set(number(2))  # change value - 2nd edit, succeeds
man1

man1.set(number(1))  # change value - 3rd edit, succeeds
man1

man1.set(number(5))  # 4th attempted edit - does NOT change the value,
man1                  # because the maximum of 3 edits has been used up

Use cases

Super variables are a good fit when you want:

A note on naming

Because a super variable’s companion functions are derived from its name by appending suffixes like .set, .rm, and .class, avoid using names that already end in a period followed by one of those words for something else in the same session, and avoid creating two super variables whose derived helper names would collide. If you need a fresh super variable under a name you’ve already used, call <name>.rm() first — newSuperVar() deliberately refuses to redeclare an existing super variable in place.

See also

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.