首页 > 解决方案 > R purrr::partial -- 它如何处理部分化的参数?

问题描述

很长一段时间以来,我一直是 Rpurrr包的热心用户,最近遇到了一个关于purrr::partial. 假设我定义了一个有两个参数的函数

f <- function(x, y) x + y

并通过将y参数设置为某个全局变量的值来部分化它:

yy <- 1
fp <- partial(f, y = !!yy)
fp(3)                       # 3 + 1 = 4

取消引用yy(即使用y = !!yy而不是y = yy)导致yy仅在创建时评估一次fp;特别是,yy在此步骤之后进行修改不会改变fp

yy <- 2
fp(3)                       # still: 3 + 1 = 4

partial这是我的问题:评估后到底做了什么yy?——我看到了两种可能性:

  1. 的值yy“硬连线”到 的主体中fp,这意味着它在被调用时不会作为参数传递fp
  2. 的值yy或多或少被视为y参数的默认值(没有覆盖默认值的选项),这意味着fp内部调用f(或它的副本),其值yy作为与匹配的参数静默传递给y. 在这种情况下fp,它只不过是围绕f.

试图探索第二种可能性,我修改了fafter defined 的定义fp。这不改变fp,意思是fp不包含任何外部引用f;但是,这并不排除fp包含旧版本的副本的(理论上)可能性f。(结论:这种方法没有帮助。)

激发我的问题的一些实际背景:在我当前的项目中,我定义了许多函数,这些函数使用(a)因调用而异的参数,(b)表示“配置数据”或“领域知识”的参数。与 (b) 参数匹配的数据(可能是相当多的数据)不会因调用而改变,但可能会在我提交更新时改变;无论如何,我相信这些数据不应该在我的函数中硬编码。我的策略是在启动时从一些文件中读取配置数据,并通过部分化 (b) 中的参数将其集成到我的函数中。通过应用偏函数purrr::pmapto some tibbles 结果有点慢,这让我怀疑在调用函数时配置数据可能仍会传递——因此我的问题。(如果有人对上面简要描述的“局部化策略”有一些想法,我也会对这些产生浓厚的兴趣。)

标签: rpurrrpartial

解决方案


似乎是选项2。尝试:

f <- function(x, y) x + y
yy <- 5
fp1 <- partial(f, y = !! yy)
debugonce(f)
fp1(3)

在这里您可以看到,如果在 RStudio 中,调试器将打开f参数x = 3y = 5传递给的原始函数。但是,偏函数不是调用真正的函数f,而是调用它的引用副本。如果您f在它被部分化后进行更改,调试器将不再找到它。

f <- function(x, y) x + y
yy <- 5
fp1 <- partial(f, y = !! yy)
f <- function(x, y) x + 2 * y
debugonce(f)
fp1(3) # debugger will not open

partial可以通过构造函数来对自己进行局部化来模仿 的行为。但是,在这种情况下,既f不会也不会yy被捕获,因此更改它们会影响您的偏函数的输出:

f <- function(x, y) x + y
yy <- 5

# similar to `partial` but captures neither `f` nor `yy`
fp2 <- function(x) f(x, yy) 
fp2(3)
#> [1] 8
# so if yy changes, so will the output of fp2
yy <- 10
fp2(3)
#> [1] 13
# and if f changes, so will the output of fp2
f <- function(x, y) x + 2 * y
fp2(3)
#> [1] 23

reprex 包(v0.3.0)于 2020 年 7 月 13 日创建


为了更好地理解它是如何工作的,我们可以通过以下方式partial构造一个函数:simple_partial

library(rlang)

f <- function(x, y) x + y
yy <- 5

simple_partial <- function(.f, ...) {
  
  # capture arguments
  args <- enquos(...)
  # capture function
  fn_expr <- enexpr(.f)
  # construct call with function and supplied arguments 
  # in the ... go all arguments which will be supplied later
  call <- call_modify(call2(.f), !!! args, ... = )
  # turn call into a quosure (= expr and environment where it should be evaluated)
  call <- new_quosure(call, caller_env())
  # create child environment of current environment and turn it into a data mask
  mask <- new_data_mask(env())
  # return this function
  function(...) {
    # bind the ... from current environment to the data mask
    env_bind(mask, ... = env_get(current_env(), "..."))
    # evaluate the quoted call in the data mask where all additional values can be found
    eval_tidy(call, mask)
  }

}

fp3 <- simple_partial(f, y = !! yy)
fp3(1)
#> [1] 6

reprex 包(v0.3.0)于 2020 年 7 月 13 日创建


推荐阅读