r-lib / lintr

Static Code Analysis for R
https://lintr.r-lib.org
Other
1.19k stars 184 forks source link

Linter to recommend using variable assignment to avoid unnecessary computations in functionals? #2612

Open IndrajeetPatil opened 3 months ago

IndrajeetPatil commented 3 months ago

Arguably, this is not the best example, but it demonstrates the inefficiency of calling functionals that unnecessarily keep recomputing something in each iteration when it needs to be computed just once and reused.

set.seed(123)
l <- list(x = rnorm(1e2), y = rnorm(1e3), z = rnorm(1e4))

bench::mark(
  "without assignment" = lapply(l, function(x) mean(x) / sd(unlist(l))),
  "with assignment" = {
    global_sd <- sd(unlist(l))
    lapply(l, function(x) mean(x) / global_sd)
  }
)[1:5]
#> # A tibble: 2 × 5
#>   expression              min   median `itr/sec` mem_alloc
#>   <bch:expr>         <bch:tm> <bch:tm>     <dbl> <bch:byt>
#> 1 without assignment   2.93ms      3ms      332.    4.48MB
#> 2 with assignment     987.2µs   1.02ms      979.  181.01KB

Created on 2024-06-19 with reprex v2.1.0

MichaelChirico commented 3 months ago

It's a great recommendation, though maybe tough to come up with much robust enough to avoid false positives.

AshesITR commented 3 months ago

This would be possible if we could somehow determine whether a function is pure or not. A completely correct implementation is probably not feasible unless functions are annotated in some way.

Untested thoughts on how to cause headaches:

l <- as.list(1:5)
meany <- function() assign("l", c(41, 42), envir = parent.frame())

lapply(l, function(x) {
  meany()
  x / sd(unlist(l))
})