"You can kill me, but you can't alter my value."
-- R. Constant
A constant is an object which value cannot be altered once assigned. There are some sound arguments for a constant; but by and large, just like strong typing or inheritance, a constant is not a very meaningful feature in data science programming.
Nonetheless, it would be quite interesting to see if it was possible to implement such feature in our language, R. The following implementation will take advantage of makeActiveBinding()
from the base package. Indeed, base R is full of treasures. Let’s have a little fun.
Active binding; how it works
What’s behind an “active binding” is essentially a function supplied by the user. The function gets invoked with very special rules: when the name is evaluated alone, the function is called with no argument passed to it; when the name apprears on the lhs of an assignment operator <-
, the function is called with the rhs as its only argument.
The following shall be a clear demonstration:
# This is a regular binding, using the ` <- ` operator
reg_binding <- function(x = "I'm a regular binding."){
print(x)
}
# `makeActiveBinding()` works like `assign()`
makeActiveBinding("act_binding",
env = parent.frame(),
fun = function(x = "I'm an active binding."){
print(x)
})
Note: the following code blocks that evaluates act_binding
contain a bug from RMarkdown, therefore the outputs are not printed.
reg_binding()
is called with no argument.
reg_binding()
## [1] "I'm a regular binding."
act_binding
also calls the underlying function with no argument.
act_binding
# "I'm an active binding."
Now reg_binding()
function is being passed an argument, overriding the default.
reg_binding("This is a nice day.")
## [1] "This is a nice day."
The rhs
string overrides the default argument of the underlying function.
act_binding <- "This is a nice day, too."
# "This is a nice day, too."
Making a constant with active binding
In order to make a constant, the object must resist being assigned a new value through defying the assignment operator. This is where active binding’s special behavior with <-
is useful. We can make it do nothing but mocking the user on his void attempt. The implementation goes as follows:
const <- function(name, value){
makeActiveBinding(substitute(name), env = parent.frame(), fun = function(.){
if (missing(.)){
return(value)
} else {
warning("You can kill me, but you can't alter my value.")
return(NULL)
}
})
}
With the const()
function, we can make a constant called foo
.
const(foo, "I'm R. Constant.")
foo
## [1] "I'm R. Constant."
foo <- "You must surrender!"
## Warning in (function (.) : You can kill me, but you can't alter my value.
OK. Now let’s kill it.
rm(foo)
Bye-bye!
P.S.
Another way to make a constant is even more straightforward using lockBinding()
.
const <- function(name, value){
assign(deparse(substitute(name)), value, envir = parent.frame())
lockBinding(substitute(name), env = parent.frame())
}
const(bar, "irish")
bar
## [1] "irish"
bar <- "bavarian"
## Error in eval(expr, envir, enclos): cannot change value of locked binding for 'bar'
See also
more on active binding:
- R and active binding (and pizza) - Colin Fay
- makeActiveBinding(): The Most Magical Hidden Gem in Base R - Yihui Xie