In R, there are the operands & and && (alternatively, | and ||), where & is vectorized, and && is not. Another difference is that & always evaluates all arguments, while && short-circuits (sometimes called lazy evaluation), meaning F && fun(x) won't call fun(x).
What I'm looking for is way to combine those two, that I can call for example
input <- data.frame(valid=c(T,T,T,F,F), value=c('1','2','3','huh',14), stringsAsFactors = F)
# A function to check evenness, but who prints an alert if the value is more then 10
fun <- function(x) {
if(any(as.numeric(x)>10))
cat(as.numeric(x)[as.numeric(x)>10], '')
return(as.numeric(x) %% 2==0)
}
cat("Numbers over 10 (unexpected):\n")
pass <- input$valid & fun(input$value)
cat("\nAnd in total we have",sum(pass),"even numbers")
Here, I get warnings, because 'huh' can't be casted to numeric, even though 'huh' is never needed to execute the function.
What I'd like is behaviour similar to this:
pass2 <- rep(FALSE, nrow(input))
cat("Numbers over 10 (unexpected):\n")
for(n in 1:nrow(input)) {
if(input$valid[n]) pass2[n] <- fun(input$value[n])
}
cat("\nAnd in total we have",sum(pass2),"even (valid) numbers")
In this example, it would be easy to adapt fun, or to write around it, but in my daily work I often find use cases with more difficult conditionals, and various functions that I don't want to adapt every time.
Is there any way to do what I want to do, or do I really need to return to non-vectorised functions and/or for-loops?
Some approaches I tried myself, but didn't work out: mapply:
mapply(`&&`, input$valid, fun(input$value))
But fun is still evaluated. It's interesting to note that the returned value IS ignored when necessary by the && if you compare the following:
mapply(`&&`, c(F,F), c(T, print('Huh?')))
mapply(`&&`, c(T,T), c(T, print('Huh?')))
mapply(`&`, c(F,F), c(T, print('Huh?')))
But in all cases the print is evaluated, I guess the mapply forces evaluation.
I also tried this:
`%&%` <- function(a,b) {
res <- rep(FALSE, times=length(a))
res[is.na(a)|a] <- a[is.na(a)|a] & b[is.na(a)|a]
}
input$valid %&% fun(input$value)
thinking I'd only use b's values if a was non-false. But it looks like almost the same thing is happening here: b is evaluated first, only then subsetted... (yes, I know I should check the lengths too, I was trying this because maybe the length-checking was forcing evaluation)