Search code examples
rfunctional-programmingpryr

Compose functions with function operators does not work as expected


In the following example I created the add_timing function operator. The input is a function (say mean) and it returns a function that does the same as mean, but reports on how long it took for the function to complete. See the following example:

library(pryr)

add_timing = function(input_function, specific_info) {
  if (missing(specific_info)) specific_info = function(l) 'That'
  function(...) {
    relevant_value = specific_info(list(...))
    start_time = Sys.time()
    res = input_function(...)
    cat(sprintf('%s took', relevant_value), difftime(Sys.time(), start_time, units = 'secs'), 'sec', '\n')
    res
  }
}
timed_mean = add_timing(mean)
# > timed_mean(runif(10000000))
# That took 0.4284899 sec 
# [1] 0.4999762

Next I tried to use pryr::compose to create the same timed_mean function (I like the syntax):

timed_mean_composed = pryr::compose(add_timing, mean)

But this does get me the required output:

# > timed_mean_composed(runif(100))
# function(...) {
#        relevant_value = specific_info(list(...))
#        start_time = Sys.time()
#        res = input_function(...)
#        cat(sprintf('%s took', relevant_value), difftime(Sys.time(), start_time, units = 'secs'), 'sec', '\n')
#        res
#      }

It seems that the compose operation does not lead to the add_timing function actually being executed. Only after calling the function, the new timed_mean_compose actually shows the correct function output.

Based on the following example from Advanced R by @HadleyWickham I expected this to work as I used it (see below for an excerpt):

dot_every <- function(n, f) {
  i <- 1
  function(...) {
    if (i %% n == 0) cat(".")
    i <<- i + 1
    f(...)
  }
}
download <- pryr::compose(
  partial(dot_every, 10),
  memoise,
  partial(delay_by, 1),
  download_file
)

Where the dot_every function operator is used in the same way I use add_timing above.

What am I missing?


Solution

  • The difference is that in your first attempt, you are calling

    (add_timing(mean))(runif(1e7)
    

    and with the compose syntax you are calling something more similar to

    add_timing(mean(runif(1e7))
    

    These are not exactly equivalent. Actually, the pryr compose function is really expanding the syntax to something more like

    x <- runif(1e7)
    x <- mean(x)
    x <- add_timing(x)
    

    Maybe looking at this will help

    a <- function(x) {print(paste("a:", x));x}
    b <- function(x) {print(paste("b:", x));x}
    x <- pryr::compose(a,b)(print("c"))
    # [1] "c"
    # [1] "b: c"
    # [1] "a: c"
    

    Notice how a isn't called until after b. This means that a would have no way to time b. compose would not be an appropriate way to create a timer wrapper.