【问题标题】:Why `missing` and default arguments are not working in functions called by `lapply`?为什么`missing`和默认参数在`lapply`调用的函数中不起作用?
【发布时间】:2013-08-08 21:46:35
【问题描述】:

我很惊讶missing 似乎无法在lapply 调用的函数中工作。假设我有以下功能:

.add <- function(x, arg, ...) {
  if (missing(arg)) {
    arg <- 1
  }
  print(match.call())
  return(x + arg)
}

wrapper <- function(l, arg, ...) {
  return(lapply(l, .add, arg=arg, ...))
}

设置arg 显式的工作方式类似于例外:

wrapper(list(x=1:10, y=1:10), arg=1)
#FUN(x = X[[1L]], arg = ..1)
#FUN(x = X[[2L]], arg = ..1)
#$x
# [1]  2  3  4  5  6  7  8  9 10 11
#
#$y
# [1]  2  3  4  5  6  7  8  9 10 11

如果没有arg,我希望得到相同的输出,但它失败了:

wrapper(list(x=1:10, y=1:10))
#FUN(x = X[[1L]], arg = ..1)
# Error in FUN(X[[1L]], ...) : argument "arg" is missing, with no default

missing 在不使用lapply 的嵌套包装函数中工作。 为什么它似乎对lapply 调用的函数没有影响?

编辑:默认参数也不起作用:

.add <- function(x, arg=5, ...) {
  if (missing(arg)) {
    arg <- 1
  }
  print(match.call())
  return(x + arg)
}

wrapper(list(x=1:10, y=1:10))
#FUN(x = X[[1L]], arg = ..1)
# Error in FUN(X[[1L]], ...) : argument "arg" is missing, with no default

看来arg 既不是缺失也不是可访问。这里发生了什么?

(我知道我可以通过在wrapper 中设置arg=NULL 和在.add 中设置if (is.null(arg)) 或其他东西来规避这个问题。.add 是一个内部函数,它根据输入(例如arg=mean(x)),我希望wrapper中的arg为用户记录参数arg并允许用户覆盖默认行为。最重要的是:我想了解为什么这不起作用!)

EDIT2:终于修复了这个行为。这是 R PR#15707。

【问题讨论】:

  • 我认为问题出在wrapper 而不是lapply。从wrapper 中完全删除形式参数arg(或给它一个默认值),它对我有用。
  • 来自文档?missing目前missing只能用于定义参数的函数的直接主体,不能用于嵌套函数或本地调用的主体.这在未来可能会改变。 这有帮助吗?
  • @joran: 没错,但.add 是一个内部函数,记录的函数是wrapperarg 的可能默认参数取决于输入.add 的子集)。
  • @Arun:你是对的,但这有效:wrapper2 &lt;- function(x, arg) { return(.add(x, arg=arg)) }
  • 我很想知道为什么会发生这种情况......我已经尝试了很长时间才能理解,但还没有。如果我发现了什么,我会回信。或者也许是时候提供我的第一个赏金了! :)

标签: r lapply


【解决方案1】:

首先,我要提一下,我认为这样做的惯用方式是构造一个调用,然后对其进行评估。有关示例,请参阅write.csv。我相信这段代码会用那种方法做你想做的事。

wrapper <- function(X, arg, ...) {
  force(X) # optional; if X is missing, the error message will be more informative
  Call <- match.call(expand.dots=TRUE)
  Call[[1L]] <- as.name("lapply")
  Call$FUN <- as.name(".add")
  eval.parent(Call)
}

好的,现在尝试解释您发现的问题。我也准备好被纠正,但希望这至少有助于澄清问题,就像@idfah 的回答一样。

首先,我将解决“默认值”问题,因为我认为它更简单。我认为这个可以更简单,如以下两个函数,其中第二个(f2)简单地调用第一个(f1)。我们看到f1 中的默认参数被f2 中对x 的承诺覆盖,并且在评估该承诺时,它丢失了。这个故事的寓意(我认为);如果调用中包含该变量,则必须在调用函数中再次设置默认值。

f1 <- function(x=1) {print(match.call()); x}
f2 <- function(x) {f1(x=x)}
f1()
## f1()
## [1] 1
f2()
## f1(x = x)
## Error in f1(x = x) : argument "x" is missing, with no default

现在讨论lapply 中的缺失问题。这里我基本上有 sgibb 的代码,但是添加了一条关于 arg 是否被认为丢失的消息。我们似乎有一个奇怪的矛盾。该消息告诉我们arg 没有丢失,但是当函数尝试访问它时,我们收到一条错误消息,告诉我们arg 丢失了。

.add <- function(x, arg) {
  print(match.call())
  if(missing(arg)) {
    message("arg is missing in .add")
    x
  } else {
    message("arg is not missing")
    x + arg
  }
}
wrapper <- function(l, arg) {lapply(l, .add, arg=arg)}
wrapper(1)
## FUN(x = 1[[1L]], arg = ..1)
## arg is not missing
## Error in FUN(1[[1L]], ...) : argument "arg" is missing, with no default

我认为正在发生的事情是lapply 将承诺放在arg..1,所以它看起来并没有丢失,但是当它尝试评估它时,它发现它丢失了。这个故事的寓意(我认为);不要试图通过lapply 传播缺失。

更新:更准确地说,它与点扩展的工作方式有关。考虑这个版本的lapply(它实际上不能在列表上工作,但在其他方面具有相同的代码风格);这表明我们得到了相同的行为。

apply3 <- function(X, FUN, ...) { 
  print(match.call())
  FUN(X, ...)
}
wrapper3 <- function(l, arg) {apply3(l, .add, arg=arg)}
wrapper3(1)
## apply3(X = l, FUN = .add, arg = arg)
## FUN(x = X, arg = ..1)
## arg is not missing
## Error in FUN(X, ...) : argument "arg" is missing, with no default

但是当我们用变量名替换点时,它会按预期工作。

apply4 <- function(X, FUN, hm) { 
  print(match.call())
  FUN(X, hm)
}
wrapper4 <- function(l, arg) {apply4(l, .add, hm=arg)}
wrapper4(1)
## apply4(X = l, FUN = .add, hm = arg)
## FUN(x = X, arg = hm)
## arg is missing in .add
## [1] 1

还有一个例子;如果我使用点,但自己进行扩展,直接调用..1,它也可以工作!这很奇怪,因为匹配的调用与不起作用的版本相同。

apply3b <- function(X, FUN, ...) { 
  print(match.call())
  FUN(X, ..1)
}
wrapper3b <- function(l, arg) {apply3b(l, .add, arg=arg)}
wrapper3b(1)
## apply3b(X = l, FUN = .add, arg = arg)
## FUN(x = X, arg = ..1)
## arg is missing in .add
## [1] 1

【讨论】:

  • This question on the R mailing list 没有任何答案,但也在探索类似的行为。
  • 非常感谢这次伟大的调查。我将通过为我自己构建调用来使用您的第一个解决方法。只剩下一个问题:为什么手动扩展(..1)有效而自动扩展失败?
  • 是的,我也有这个问题,不过目前我的好奇心已经得到满足。我的猜测是,必须深入研究 C 代码才能真正理解点扩展的工作原理。
  • 感谢@sgibb 的所有研究和有趣的问题。似乎具有默认值的(正式)参数仍被视为missing(只是在调试函数时注意到这一点并偶然发现了这个问答)。例如,f1 &lt;- function(x=1) missing(x); f1()
  • @Aaron:从 R 3.2.0 开始,一切都按预期工作。这是 R 中的一个错误:PR#15707
【解决方案2】:

你的包装器中没有missing,所以它在那里爆炸。在这种情况下,您不需要它,因为无论如何您都在使用可变参数。试试这个:

.add <- function(x, arg, ...) {
    if (missing(arg)) 
      arg <- 1
    print(match.call())
  return(x + arg)
}

wrapper <- function(l, ...) 
  return(lapply(l, .add, ...))

如果包装器需要知道arg,那么你需要一个missing

.add <- function(x, arg, ...) {
  print(match.call())
  return(x + arg)
}

wrapper <- function(l, ...) {
  if (missing(arg)) 
    arg <- 1
  return(lapply(l, .add, arg=arg, ...))
}

我的立场是正确的

以下示例允许missing 位于调用堆栈的底部,这可能是因为惰性求值。我不确定为什么你的例子不起作用......很好奇。

wrapper.c <- function(l, arg)
{
  if (missing(arg))
    arg <- 1
  print("I'm in c")
  arg
}

wrapper.b <- function(l, arg)
{
  print("I'm in b")
  wrapper.c(l, arg)
}

wrapper.a <- function(l, arg)
  wrapper.b(l, arg)

> wrapper.a(1)
[1] "I'm in b"
[1] "I'm in c"
[1] 1

【讨论】:

  • 你是对的,但它并没有真正回答我的问题。顺便说一句,arg 的默认值取决于输入.add 的子集(例如mean(x) 之类的东西)。
  • 您是否也看到了我提供的两个解决方案中的第一个?也许我不明白你的问题。如果您将arg 作为wrapper 的参数,但在包装器中没有missing,那么您的程序在调用.add 之前就爆炸了,这对我来说似乎是合乎逻辑的。如果您对 wrapper 的参数都是可变参数,那么请去掉 wrapper 中的 arg
  • 我的函数在调用.add 之前不会bomb,否则你将看不到print(match.call()) 的输出。恕我直言,arg 在用于return(x + arg) 时首先被评估(惰性评估)。
  • 你是对的,我的立场是正确的......虽然我仍然不确定你为什么要这样做。
猜你喜欢
  • 2021-09-03
  • 1970-01-01
  • 2018-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-01
  • 1970-01-01
相关资源
最近更新 更多