下面的函数由于递归默认参数引用而引发错误。这是预期的行为,因为默认参数是在函数范围内计算的。
i = 1
f1 = function(i=i) i
f1() # Error: promise already under evaluation: recursive default argument reference
但是如果
i
在单括号 [
内,那么就没有错误,并且函数返回 x
而不是预期的 x[i]
:
x = 1:5
i = 1:3
f2 = function(x, i=i) x[i]
f2(x) # No error, but returns x, not x[i]
这是一个无声错误,会导致微妙的错误。例如,在机器学习中,我们可能认为我们正在对训练集进行子集化 (
x[i]
),但实际上同时使用了训练集和测试集 (x
)。即使变量 i
不存在,该行为也会发生:
x = 1:5
if (exists("i")) rm(i)
f3 = function(x, i=i) x[i]
f3(x) # No error, but returns x, even if i doesnt exist.
更合理的行为是当
i
位于双括号内时([[
而不是 [
),这会引发 missing subscript
错误:
x = 1:5
i = 1:3
f4 = function(x, i=i) x[[i]]
f4(x) # Error: missing subscript
我的问题是:
f2
和f3
的行为是有意为之还是一个错误?f2
的行为。这是对我原来答案的编辑:
我认为这不是一个错误,尽管你可以说这是一个设计缺陷。
首先,对于
x[i]
和 x[[i]]
行为之间的差异有一个简单的解释:x[]
是合法的,并且返回 x
。 x[[]]
不合法,因为它说提取某些东西,但没有说提取什么。
现在,为什么我说这不是一个错误?看一下这个例子,比你的简单一点,并且没有使用原始函数
[
:
f <- function(farg = farg)
if (missing(farg))
message("Not an error: farg is missing")
g <- function(garg)
f(garg)
g()
#> Not an error: farg is missing
创建于 2023-12-05,使用 reprex v2.0.2
函数
f()
使用 missing(farg)
测试其参数。这不会评估默认值,它只是报告参数是否丢失。所以f()
永远不会尝试评估farg
。
函数
[
就像f
:如果索引丢失,它只返回整个向量,它不会尝试评估索引。由于它从不尝试评估 i
,因此不会生成错误。
新增:
但是这个解释并不完整。 @Roland 建议查看这样的代码:(我更改了名称以更紧密地匹配我的示例):
g1 <- function(x, i=1)
x[i]
g1(1:5)
#> [1] 1
创建于 2023-12-05,使用 reprex v2.0.2
这里,在对
i
的调用中缺少 g1()
,但现在它有一个默认值。因此,当 R 尝试评估 x[i]
时,它将检查 i
是否缺失。它在 g1()
的上下文中进行评估,其中 missing(i)
将返回 true,但现在我们有一个默认值,因此缺失不会传播,我们用默认值 1 代替 i
并最终评估x[1]
。
现在,如果默认值是
i = i
,就像原来的问题一样,会怎么样?现在,当确定 i
中是否缺少 x[i]
时,R 将替换默认值并进行检查。它发现,是的,缺少 i
,因此 x[i]
返回与 x[]
相同的内容。
那么为什么它是一个设计缺陷呢?我的第一个答案是,存在一个问题:缺失是否应该通过函数调用传播。 “显然”参数在
f(garg)
中没有缺失,但 R 认为它缺失,因为 garg
缺失了。如果缺失取决于调用的形式而不是其中的值,您将得到预期的结果。但这不是 R 的工作原理。
我的第二个答案是,这是一个缺陷,因为缺失的传播是以一种非常微妙的方式处理的。在决定传播什么之前,具有默认值的参数将被替换为其默认值。