R 中的“{”class 是什么?

What is "{" class in R?

代码如下:

mf = function(..., expr) {
    expr = substitute(expr)
    print(class(expr))
    print(str(expr))
    expr
}
mf(a = 1, b = 2, expr = {matrix(NA, 4, 4)})

输出:

[1] "{"
length 2 {  matrix(NA, 4, 4) }
 - attr(*, "srcref")=List of 2
  ..$ :Class 'srcref'  atomic [1:8] 1 25 1 25 25 25 1 1
  .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x7fbcdbce3860> 
  ..$ :Class 'srcref'  atomic [1:8] 1 26 1 41 26 41 1 1
  .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x7fbcdbce3860> 
 - attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x7fbcdbce3860> 
 - attr(*, "wholeSrcref")=Class 'srcref'  atomic [1:8] 1 0 1 42 0 42 1 1
  .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x7fbcdbce3860> 
NULL
{
matrix(NA, 4, 4)
}

显然 substitute(expr) 的结果产生了 class“{”的结果。 class 到底是什么?为什么 {matrix(NA, 4, 4)} 的长度是 2?这些奇怪的属性是什么意思?

{ 是代码块的 class。只看 classes,注意它们之间的区别

mf(a = 1, b = 2, expr = {matrix(NA, 4, 4)})
# [1] "{"
mf(a = 1, b = 2, expr = matrix(NA, 4, 4))
# [1] "call"

{ 的 class 可以包含多个语句。 length() 表示块中有多少条语句(包括块的开头)。例如

length(quote({matrix(NA, 4, 4)}))
# [1] 2
length(quote({matrix(NA, 4, 4); matrix(NA,3,3)}))
# [1] 3
length(quote({}))
# [1] 1

属性 "srcref" 和 "srcfile" 是 R 如何跟踪定义函数的位置以尝试提供信息性错误消息的方式。您可以查看 ?srcfile 帮助页面以获取更多相关信息。

您所看到的反映了 R 通过其自身的数据结构公开其内部语言结构的方式。

substitute() 函数 return 是 R 表达式的解析树。解析树是语言元素的树。这些可以包括文字值、符号(基本上是变量名)、函数调用和大括号块。这是 return 由 substitute() 编辑的所有 R 语言元素的演示,显示了它们在所有 R 类型 class化方案中的类型:

tmc <- function(x) c(typeof(x),mode(x),class(x));
tmc(substitute(TRUE));
## [1] "logical" "logical" "logical"
tmc(substitute(4e5L));
## [1] "integer" "numeric" "integer"
tmc(substitute(4e5));
## [1] "double"  "numeric" "numeric"
tmc(substitute(4e5i));
## [1] "complex" "complex" "complex"
tmc(substitute('a'));
## [1] "character" "character" "character"
tmc(substitute(somevar));
## [1] "symbol" "name"   "name"
tmc(substitute(T));
## [1] "symbol" "name"   "name"
tmc(substitute(sum(somevar)));
## [1] "language" "call"     "call"
tmc(substitute(somevec[1]));
## [1] "language" "call"     "call"
tmc(substitute(somelist[[1]]));
## [1] "language" "call"     "call"
tmc(substitute(somelist$x));
## [1] "language" "call"     "call"
tmc(substitute({blah}));
## [1] "language" "call"     "{"

备注:

  • 请注意所有三种类型 class化方案非常相似,但又略有不同。这可能会造成混淆。 typeof()给出了对象的存储类型,有时也叫"internal"类型(说实话,可能不应该叫"internal"因为它 经常非常直接地暴露给 R 级别的用户,但通常是这样描述的;我将其称为 "fundamental" 或 "underlying" 类型),mode() 给出了类似的 class每个人都应该忽略的化方案,而class()给出对象的隐式(如果没有class属性)或显式(如果有)class,这是用于 S3 方法查找(而且,应该说,有时直接由 R 代码检查,独立于 S3 查找过程)。
  • 请注意 TRUE 是一个逻辑文字,但 T 是一个符号,就像任何其他变量名一样,并且恰好默认分配给 TRUE(并且FFALSE 同上)。这就是为什么有时人们建议不要使用 TF 而倾向于使用 TRUEFALSE,因为 TF 可以重新分配(但我个人更喜欢使用 TF 来简化;任何人都不应该重新分配它们!)。
  • 精明的 reader 会注意到,在我对文字的演示中,我省略了原始类型。这是因为 R 中没有原始文字这样的东西。事实上,在 R 中获取原始向量的方法非常少; raw()as.raw()charToRaw()rawConnectionValue() 是我知道的唯一方法,如果我在 substitute() 调用中使用这些函数,它们将被 returned 作为 "call" 对象,就像在 sum(somevar) 示例中一样,而不是文字原始值。列表类型也是如此;没有列表文字这样的东西(尽管有很多方法可以通过函数调用获取列表)。纯原始向量 return 'raw' 用于所有三种类型 class 化,以及纯列表 return 'list' 用于所有三种类型 class 化。

现在,当你有一个比简单的文字值或符号更复杂的分析树时(意味着它必须是一个函数调用或花括号表达式),你通常可以通过强制检查分析树的内容列表。这就是 R 通过自己的数据结构公开其内部语言结构的方式。

深入了解您的示例:

pt <- as.list(substitute({matrix(NA,4,4)}));
pt;
## [[1]]
## `{`
##
## [[2]]
## matrix(NA, 4, 4)

这很清楚为什么 length() returns 2:这是表示解析树的列表的长度。通常,表达式的大括号被翻译成第一个列表组件,其余列表组件由大括号内以分号分隔的语句构建:

as.list(substitute({}));
## [[1]]
## `{`
##
as.list(substitute({a}));
## [[1]]
## `{`
##
## [[2]]
## a
##
as.list(substitute({a;b}));
## [[1]]
## `{`
##
## [[2]]
## a
##
## [[3]]
## b
##
as.list(substitute({a;b;c}));
## [[1]]
## `{`
##
## [[2]]
## a
##
## [[3]]
## b
##
## [[4]]
## c

请注意,这与函数调用的工作方式相同,不同之处在于,对于函数调用,列表组件由函数调用的逗号分隔参数构成:

as.list(substitute(sum()));
## [[1]]
## sum
##
as.list(substitute(sum(1)));
## [[1]]
## sum
##
## [[2]]
## [1] 1
##
as.list(substitute(sum(1,3)));
## [[1]]
## sum
##
## [[2]]
## [1] 1
##
## [[3]]
## [1] 3
##
as.list(substitute(sum(1,3,5)));
## [[1]]
## sum
##
## [[2]]
## [1] 1
##
## [[3]]
## [1] 3
##
## [[4]]
## [1] 5

从上面可以清楚地看出,第一个列表组件实际上是一个代表函数名称的符号,用于大括号表达式和函数调用。换句话说,左大括号 一个函数调用,它只是 return 它的最后一个参数。正如方括号是在其之上构建了方便语法的普通函数调用一样,左大括号是在其之上构建了便捷语法的普通函数调用:

a <- 4:6;
a[2];
## [1] 5
`[`(a,2);
## [1] 5
{1;2};
## [1] 2
`{`(1,2);
## [1] 2

回到你的例子,我们可以通过遍历表示解析树的列表结构来充分探索解析树。我刚刚写了一个很好的小递归函数,可以很容易地做到这一点:

unwrap <- function(x) if (typeof(x) == 'language') lapply(as.list(x),unwrap) else x;
unwrap(substitute(3));
## [1] 3
unwrap(substitute(a));
## a
unwrap(substitute(a+3));
## [[1]]
## `+`
##
## [[2]]
## a
##
## [[3]]
## [1] 3
##
unwrap(substitute({matrix(NA,4,4)}));
## [[1]]
## `{`
##
## [[2]]
## [[2]][[1]]
## matrix
##
## [[2]][[2]]
## [1] NA
##
## [[2]][[3]]
## [1] 4
##
## [[2]][[4]]
## [1] 4

如您所见,花括号表达式变成函数 `{`() 的正常函数调用,接受一个参数,即您在其中编写的单个语句。该语句由对 matrix() 的单个函数调用组成,采用三个参数,每个参数都是文字值:NA44。这就是整个解析树。

所以现在我们可以在更深层次上理解"{" class的含义:它表示解析树的一个元素,它是对`{`()函数的函数调用.它恰好是 classed 不同于其他函数调用("{" 而不是 "call"),但据我所知,这在任何地方都没有意义。还观察到 typeof()mode() 在表示函数调用的所有解析树元素之间是相同的(分别为 "language""call",对于 `{`() 和其他一样。