In R, operations with booleans are evaluated in a short-circuit manner. This basically means that
if (x && y)
{
# do something
}
is equivalent to
if (x)
{
if (y)
{
# do something
}
}
that is, if x is not true, y is not evaluated anymore. This seems to be common to other programming languages which use lazy evaluation.
Here a few examples:
# the left-hand side is FALSE, no need to evaluate NA
# see also help("&&")
FALSE && NA
# FALSE
# the left-hand side is TRUE, so NA gets evaluated this time
TRUE && NA
# NA
is_awake <- function()
{
message("still sleeping")
FALSE
}
is_happy <- function()
{
message("being happy")
TRUE
}
# is_happy is TRUE => is_awake also needs to be checked
if (is_happy() && is_awake()) {
message("iupi")
}
# being happy
# still sleeping
# is_awake is FALSE, so no need to check if is_happy
if (is_awake() && is_happy()) {
message("iupi")
}
# still sleeping
Due to short-circuit evaluation, you can theoretically speed up your programs if you put on the left side variables that are less likely to be true (compared to the right-side variables).
As I was familiar to this concept, I naively assumed that this happens all the time in R. So the following was a surprise to me:
dt <- data.table::data.table(
value_type = c("numeric", "numeric", "string"),
value = c("41", "42", "a")
)
dt[value_type == "numeric" & as.numeric(value) == 42]
# value_type value
# 1: numeric 42
# Warning message:
# In eval(.massagei(isub), x, ienv) : NAs introduced by coercion
Even if all the rows in dt with value_type == "numeric" can be successfully converted to a number, a warning is issued, because the value "a" cannot be converted into numeric, so short-circuit evaluation does not work inside a data.table. My guess is that it has to do with the way evaluation is done inside a data.table, and probably related to the fact that the heavy part of the data.table code is in C, which does not use short-circuit evaluation. This is a tiny price to pay for the efficiency of data tables.
You might want to suppress the warning in the code above, since you (kind of) understand where it comes from. I would however never suppress all the warnings - they usually are (or should be) a useful tool, trying to make you pay more attention. I rather suggest something like this:
suppress_specific_warning <- function(expr, pattern)
{
checkmate::assertString(x = pattern)
withCallingHandlers(
expr = expr,
warning = function(w)
{
if (grepl(pattern = pattern, w$message)) {
tryInvokeRestart("muffleWarning")
}
}
)
}
suppress_specific_warning(
pattern = "NAs introduced by coercion",
{
dt[value_type == "numeric" & as.numeric(value) == 42]
}
)
# value_type value
# 1: numeric 42
If you want to know more about warnings and restarts, you might want to have a look here.
As I learned on the way, I am not alone in believing wrong things. Here two examples:
Make a promise. Show up. Do the work. Repeat.