1

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?

Paul Hiemstra
  • 59,984
  • 12
  • 142
  • 149

2 Answers2

3

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.

Thomas
  • 43,637
  • 12
  • 109
  • 140
MrFlick
  • 195,160
  • 17
  • 277
  • 295
1

The issue is that pryr::compose is aimed at doing something completely different from what you're trying to do in your initial example. You want to create a function factory (called add_timing), which will take a function as input and return a new function as output that does the same thing as the input function but with an additional time printing. I would write that as follows:

add_timing <- function(FUN) { function(...) { print(system.time(r <- FUN(...))); r }}
mean(1:5)
# [1] 3
add_timing(mean)(1:5)
#    user  system elapsed 
#       0       0       0 
# [1] 3

The compose function, by contrast, returns a function that represents a series of functions to be evaluated in sequence. The examples in ? compose are helpful here. Here's an example that builds on that:

add1 <- function(x) x + 1
times2 <- function(x) x * 2

# the following two are identical:
add1(1)
# [1] 2
compose(add1)(1)
# [1] 2

# the following two are identical:
times2(1) 
# [1] 2
compose(times2)(1)
# [1] 2

compose becomes useful for nesting, when the order of nesting is important:

add1(times2(2))
# [1] 5
compose(add1, times2)(2)
# [1] 5

times2(add1(2))
# [1] 6
compose(times2, add1)(2)
# [1] 6

This means that the reason your example does not work is because your functions are not actually nested in the way that compose is intended to work. In your example, you're asking system.time to, for example, calculate the time to evaluate 3 (the output of mean) rather than the time to evaluate mean(1:5).

Thomas
  • 43,637
  • 12
  • 109
  • 140
  • Thanks for the feedback. My question is then why the `dot_every` function factory does work, and mine does not. – Paul Hiemstra Aug 31 '15 at 19:10
  • @PaulHiemstra I think I would stay away from the `dot_every` example here because there's a lot going on given the use of `partial` and the fact that `dot_every` is using assignment into a function's environment. It is rather obscure; as an example, run: `f <- dot_every(3, mean); environment(f)$i; f(1); f(1); f(1); environment(f)$i` – Thomas Aug 31 '15 at 19:34