为什么当由于“递归默认参数引用”而缺少“i”时,“x[i]”返回“x”?

问题描述 投票:0回答:1

下面的函数由于递归默认参数引用而引发错误。这是预期的行为,因为默认参数是在函数范围内计算的

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
    的行为是有意为之还是一个错误?
  • 如果这不是一个错误,那么有人可以解释其背后的原因吗?我简单地浏览了 R 子集的源代码,但我对 C 的了解还不足以理解
    f2
    的行为。
r function lazy-evaluation
1个回答
2
投票

这是对我原来答案的编辑:

我认为这不是一个错误,尽管你可以说这是一个设计缺陷。

首先,对于

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 的工作原理。

我的第二个答案是,这是一个缺陷,因为缺失的传播是以一种非常微妙的方式处理的。在决定传播什么之前,具有默认值的参数将被替换为其默认值。

© www.soinside.com 2019 - 2024. All rights reserved.