第二次运行此函数时,它会被R的JT编译器编译为字节码.然而,为什么这会产生这种精确的效果取决于您正在调用的函数和您正在提供的参数.
is.null()
does not take a y
argument
首先要理解的是,错误消息中引用的x
不是函数中的x
,而是is.null()
期望作为参数的x
.函数签名是:
is.null
# function (x) .Primitive("is.null")
每当我们向原始函数提供它不期望的命名参数时,我们就可以出现这个特定错误:
is.null(y = NULL)
# Error in is.null(y = NULL) :
# supplied argument name 'y' does not match 'x'
abs(y = 1)
# Error in abs(y = 1) : supplied argument name 'y' does not match 'x'
我不完全清楚您试图用此语法做什么.如果您想将NULL
分配给y
,那么您需要is.null(y <- NULL)
,这不会产生错误.
让我们将函数的参数重命名为a
,以减少x
附近的任何歧义,并删除不必要的加法操作:
tmp_func <- function(a = 1) {
if (!is.null(y = NULL)) {}
return(a)
}
tmp_func()
# Error in is.null(y = NULL) :
# supplied argument name 'y' does not match 'x
tmp_func()
# [1] 1
我们可以看到它仍然在抱怨x
,而不是a
.
为什么错误第二次消失
问题变成了为什么我们第二次没有看到这个错误.原因是因为R有一个实时(JT)compiler,它将常用函数编译为字节码.您可以使用oldJit <- compiler::enableJIT(0)
判断您的级别设置为哪个级别并判断oldJit
(这会将级别设置为0
,以便您稍后可能想将其更改回来).我怀疑至少是2
:
在1级,大型函数将在首次使用之前进行编译.在第2级,小函数在第二次使用之前也会被编译.(source)
编译该函数会更改它处理此运算式的方式
第二次运行函数时,它将被编译.如果我们在第一次和第二次调用后打印函数源代码,我们可以看到这一点:
tmp_func <- function(a = 1) {
if (!is.null(y = NULL)) {}
return(a)
}
tmp_func()
# Error in is.null(y = NULL) :
# supplied argument name 'y' does not match 'x
tmp_func # print source
# function(a = 1) {
# if (!is.null(y = NULL)) {}
# return(a)
# }
tmp_func()
# [1] 1
tmp_func # print source again
# function(a = 1) {
# if (!is.null(y = NULL)) {}
# return(a)
# }
# <bytecode: 0x3f42e28>
请注意,最后一行显示该函数现在已被编译为字节码.编译器似乎不关心参数名称,因为它跳过了R函数,而且它与C代码中.Primitive("is.null")
的接口方式也不关心.我们可以在docs中看到,不变的NULL
参数是一种特殊情况:
常数参数按cmpConstArg
编制.同样,对于常见的特殊常数NULL
、TRUE
和FALSE
有特殊指令.(p13)
您可以自己测试这一点-例如,如果将其更改为is.null(y = "a")
,则已编译的代码的行为与已解释的代码相同.
然而,在这种特殊情况下,编译器只看到!is.null()
应用于NULL
的常数.我们可以在生成的指令中看到:
compiler::disassemble(tmp_func)
list(12L, BASEGUARD.OP, 1L, 6L, LDNULL.OP, ISNULL.OP,
NOT.OP, 4L, BRIFNOT.OP, 5L, 14L, LDNULL.OP, GOTO.OP, 15L,
LDNULL.OP, POP.OP, GETVAR.OP, 7L, RETURN.OP)
这是奇怪的行为,我认为从技术上讲这是一个编译器错误.但is.null(y = NULL)
并不是一个真正有意义的表达,也不是我希望看到的,所以它看起来不是一个非常重要的表达.在任何情况下,如果您想避免这种情况,您可以更改您的JT compilation levels:
compiler::enableJIT(0)