使用函数运算符组合函数无法按预期工作
Compose functions with function operators does not work as expected
在下面的示例中,我创建了 add_timing
函数运算符。输入是一个函数(比如 mean
),它 returns 是一个与 mean
功能相同的函数,但会报告该函数完成所花费的时间。请参阅以下示例:
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
接下来我尝试使用 pryr::compose
创建相同的 timed_mean
函数(我喜欢语法):
timed_mean_composed = pryr::compose(add_timing, mean)
但这确实让我得到了所需的输出:
# > 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
# }
似乎组合操作并没有导致实际执行add_timing
函数。只有在调用函数后,新的 timed_mean_compose
才真正显示出正确的函数输出。
基于@HadleyWickham 的 Advanced R 中的 following example,我希望它在我使用它时能够正常工作(摘录见下文):
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
)
其中 dot_every
函数运算符的使用方式与我上面使用 add_timing
的方式相同。
我错过了什么?
不同之处在于,在您第一次尝试时,您调用的是
(add_timing(mean))(runif(1e7)
并且使用 compose
语法,您调用的内容更类似于
add_timing(mean(runif(1e7))
这些并不完全等同。实际上,pryr
compose 函数实际上将语法扩展为更像
的语法
x <- runif(1e7)
x <- mean(x)
x <- add_timing(x)
也许看看这个会有帮助
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"
请注意 a
是如何在 b
之后才被调用的。这意味着 a
将无法计时 b
。 compose
不是创建计时器包装器的合适方法。
问题是 pryr::compose
旨在做一些与您在初始示例中尝试做的事情完全不同的事情。你想创建一个函数工厂(称为 add_timing
),它将一个函数作为输入,return 一个新函数作为输出,它与输入函数做同样的事情,但有一个额外的时间打印。我会这样写:
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
compose
函数,相比之下,return 是一个函数,表示一系列要按顺序求值的函数。 ? compose
中的示例在这里很有帮助。这是一个基于此的示例:
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
对嵌套很有用:
add1(times2(2))
# [1] 5
compose(add1, times2)(2)
# [1] 5
times2(add1(2))
# [1] 6
compose(times2, add1)(2)
# [1] 6
这意味着您的示例不起作用的原因是因为您的函数实际上并未按照 compose
预期的方式嵌套。例如,在您的示例中,您要求 system.time
计算评估 3
的时间(mean
的输出)而不是评估 mean(1:5)
的时间。
在下面的示例中,我创建了 add_timing
函数运算符。输入是一个函数(比如 mean
),它 returns 是一个与 mean
功能相同的函数,但会报告该函数完成所花费的时间。请参阅以下示例:
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
接下来我尝试使用 pryr::compose
创建相同的 timed_mean
函数(我喜欢语法):
timed_mean_composed = pryr::compose(add_timing, mean)
但这确实让我得到了所需的输出:
# > 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
# }
似乎组合操作并没有导致实际执行add_timing
函数。只有在调用函数后,新的 timed_mean_compose
才真正显示出正确的函数输出。
基于@HadleyWickham 的 Advanced R 中的 following example,我希望它在我使用它时能够正常工作(摘录见下文):
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
)
其中 dot_every
函数运算符的使用方式与我上面使用 add_timing
的方式相同。
我错过了什么?
不同之处在于,在您第一次尝试时,您调用的是
(add_timing(mean))(runif(1e7)
并且使用 compose
语法,您调用的内容更类似于
add_timing(mean(runif(1e7))
这些并不完全等同。实际上,pryr
compose 函数实际上将语法扩展为更像
x <- runif(1e7)
x <- mean(x)
x <- add_timing(x)
也许看看这个会有帮助
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"
请注意 a
是如何在 b
之后才被调用的。这意味着 a
将无法计时 b
。 compose
不是创建计时器包装器的合适方法。
问题是 pryr::compose
旨在做一些与您在初始示例中尝试做的事情完全不同的事情。你想创建一个函数工厂(称为 add_timing
),它将一个函数作为输入,return 一个新函数作为输出,它与输入函数做同样的事情,但有一个额外的时间打印。我会这样写:
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
compose
函数,相比之下,return 是一个函数,表示一系列要按顺序求值的函数。 ? compose
中的示例在这里很有帮助。这是一个基于此的示例:
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
对嵌套很有用:
add1(times2(2))
# [1] 5
compose(add1, times2)(2)
# [1] 5
times2(add1(2))
# [1] 6
compose(times2, add1)(2)
# [1] 6
这意味着您的示例不起作用的原因是因为您的函数实际上并未按照 compose
预期的方式嵌套。例如,在您的示例中,您要求 system.time
计算评估 3
的时间(mean
的输出)而不是评估 mean(1:5)
的时间。