如何创造一些奇异的类型?

jak*_*kub 15 r

我对 感兴趣?typeof,它提到了可以返回的值。有没有办法打电话typeof(something)并获得以下其中一项?

“承诺”、“字符”、“...”、“任何”、“字节码”

我发现我可以获得两种更奇特的类型,帮助认为typeof“不太可能在用户级别看到”,如下所示:

typeof(new("externalptr"))
# [1] "externalptr"

typeof(rlang::new_weakref(new("externalptr")))
# [1] "weakref"
Run Code Online (Sandbox Code Playgroud)

但有办法得到其他人吗?

All*_*ron 38

在我们尝试从 中获取具体响应之前typeof,让我们先澄清一下该函数的实际用途。这需要回顾一下什么是类型R 中的

\n

类型

\n

每个 R 对象都由底层 C 代码中称为 an 的结构表示SEXP,其中包含指向实际数据的指针。由于可以指向不同类型的数据结构,因此每种数据结构都有一个名为 的字段,该字段告诉 R所指向的结构类型。它存储为整数。SEXPSEXPSEXPTYPESEXPSEXPTYPE

\n

当我们调用R 时,会在类型表中查找typeof对象的整数值,最终将字符串返回到控制台,以给出对象的人类可读的描述。因此,类型表包含的所有可能输出。SEXTYPESEXPTYPEtypeof

\n

从这个意义上说, R 中对象的类型是对它是什么类型的对象的最低级别的描述。

\n

类型表中条目的值SEXPTYPE如下:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n
价值性别类型描述typeof输出
0NILSXP无效的“无效的”
1SYMSXP符号“象征”
2列表XP配对表“配对列表”
3闭环XP关闭“关闭”
4环境变量XP环境“环境”
5PROMSXP承诺“承诺”
6朗斯XP语言对象“语言”
7特别SXP特殊功能“特别的”
8内置XP内置函数“内置”
9CHARSXP内部字符串“字符”
10LGLSXP逻辑向量“逻辑”
13INTSXP整数向量“整数”
14RELSXP数值向量“双倍的”
15CPLXXXP复杂向量“复杂的”
16STRSXP字符向量“特点”
17 号DOTSXP点-点-点对象“……”
18任何SXP使 \xe2\x80\x9cany\xe2\x80\x9d 参数工作“任何”
19VECSXP列表(通用向量)“列表”
20EXPRSXP表达载体“表达”
21BCODEXXP字节码“字节码”
22EXPTRSXP外部指针“外部指针”
23弱引用XP弱引用“弱引用”
24RAWSXP原始向量“生的”
25S4SXPS4 类不是简单类型“S4”
\n
\n

可以在控制台中获取每种类型的对象,但据我所知,其中四种无法单独在基础 R 中获取它们是“promise”、“char”、“any”和“weakref”。对于这些,我们需要使用额外的编译代码 - 要么是我们自己的小片段Rcpp,要么是 中已经可用的函数rlang

\n

让我们在控制台中获取每个有效类型的示例。

\n

0:NILSXP

\n

这只是NULL

\n
n <- NULL\ntypeof(n)\n#> [1] "NULL"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

1:SYMSXP

\n

这是一个未评价的symbol。我们可以通过多种方式在基数 R 中获得符号,包括quotesubstitutebquotestr2lang

\n
s <- quote(x)\ntypeof(s)\n#> [1] "symbol"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

2:列表XP

\n

尽管有这个名称,但这并不用于对象list,而是用于点对列表,如formalsof 函数中使用的那样。从功能上讲,它们与标准列表类似,但在底层 C 代码中实现方式不同,并且确实有一些重要的区别

\n
p <- pairlist(a = 1)\ntypeof(p)\n#> [1] "pairlist"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

3:关闭XP

\n

它用于存储闭包,即用 R 代码编写的函数,而不是内部 C 函数。

\n
f <- function() {}\ntypeof(f)\n#> [1] "closure"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

4:环境XP

\n

用于存储环境

\n
e <- new.env()\ntypeof(e)\n#> [1] "environment"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

5:PROMSXP

\n

在 R 中,promise由两个对象组成:一块未评估的代码,加上一个指向应在其中评估该代码的环境的指针。这与quosuretidyverse 生态系统中的 a 非常相似,除了可以很容易地分配和传递 quosure,延迟评估直到需要时为止。承诺更加转瞬即逝;一旦您将其分配给一个符号,它就会立即进行评估,因此要在野外查看一个符号,您需要将其包含在列表中。

\n

我找不到一种仅使用基础 R 创建一个的方法,但我们可以使用rlang::node_cara获得承诺DOTSXP

\n
p <- list(rlang::node_car((function(...) get("..."))(a = 1)))\n\np\n#> [[1]]\n#> <promise: 0x0000021e05c3f870>\n\ntypeof(p[[1]])\n#> [1] "promise"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

6:LANGSXP

\n

这只是一段未评估的代码(尽管在存储之前已将其解析为语法正确)。同样,这可以通过quote或创建substitute,但公式也存储为语言对象:

\n
l <- hello ~ world\ntypeof(l)\n#> [1] "language"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

7:特殊XP

\n

这仅用于将未计算的参数传递给内部 R 机制的原始函数:

\n
i <- `if`\ntypeof(i)\n#> [1] "special"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

8:内置XP

\n

同样,这仅用于存储内置函数,但它们与“特殊”函数不同,因为它们的参数在传递给内部代码之前在 R 中进行计算。

\n
b <- `+`\ntypeof(b)\n#> [1] "builtin"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

9: 字符XP

\n

它们用于存储 R\ 熟悉的字符向量,而是 R 内部用于存储原子字符串的字符类型。这允许缓存可重用的字符串,并允许字符向量(类型STRSXP)更加高效。请注意,R 不喜欢处理CHARSXP其内部函数之外的内容。当你在控制台中有一个时,它会发出警告,告诉你这种类型的对象不能有属性。

\n

与直觉相反,这是最难制作的之一。也许最简单的方法是创建一个RAWSXP然后更改编译代码中的基础类型。

\n
Rcpp::cppFunction("SEXP mkchar(SEXP s) {SET_TYPEOF(s, 9); return s;}")\n\nget_char <- function(){\n  mkchar(as.raw(c(0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, \n           0x6c, 0x64, 0x21, 0x00)))\n}\n\nchr <- get_char()\n\nchr\n#> <CHARSXP: "Hello World!">\n\ntypeof(chr)\n#> [1] "char"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

10:LGLSXP

\n

这些是R中常用的逻辑向量。

\n
l <- TRUE\ntypeof(l)\n#> [1] "logical"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

如果您想知道 SEXPTYPE 11 和 12,它们以前在几十年前用于因子和有序因子,但甚至不再定义,因此我们没有可以从中提取与typeof这些相对应的隐藏遗留类型隐藏遗留类型。

\n
\n

13:INTSXP

\n

R对整数和双精度浮点数使用不同的类型,但只要有一点点的刺激就会从整数转换为双精度数。整数和双精度数之间的差异在某种程度上从最终用户那里抽象出来,因为INTSXPREALSXP被集中在一起作为众数 "numeric"

\n
I <- 1L\ntypeof(I)\n#> [1] "integer"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

14:REALSXP

\n

这些是熟悉的数值向量

\n
r <- 1.1\ntypeof(r)\n#> [1] "double"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

15:CPLXSXP

\n

复数有自己的存储类型并且很容易创建。我不确定为什么他们需要自己的存储类型,因为它们似乎可以在 S3 中轻松实现。据推测,这部分是历史原因,部分是由于与各种数学库接口的效率。

\n
C <- 1 + 1i\ntypeof(C)\n#> [1] "complex"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

16:STRSXP

\n

是熟悉的字符向量。

\n
s <- "Hello world"\ntypeof(s)\n#> [1] "character"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

17:DOTSXP

\n

某些函数形式中允许传递额外的任意参数的点被实现为承诺对列表。它有自己的存储模式,称为 DOTSXP

\n

也许令人惊讶的是,这实际上可以在没有任何编译代码的情况下获得:

\n
d <- (function(...) get("..."))(a = 1)\n\nd\n#> <...>\n\ntypeof(d)\n#> [1] "..."\n
Run Code Online (Sandbox Code Playgroud)\n
\n

18:任何SXP

\n

这并不是一个真正定义明确的存储模式。据我所知,它在内部用作替代品,主要用于 S4 对象的实现。如果您尝试显示“any”类型的对象,控制台将给出错误,但它可以被存储并且其类型可以正确报告。我看不到一种方法来获取它,而无需通过编译代码强制现有对象:

\n
Rcpp::cppFunction("SEXP get_any(SEXP s) {SET_TYPEOF(s, 18); return s;}")\n\na <- get_any(1:5)\n\na\n#> Error: unimplemented type \'any\' in \'PrintValueRec\'\n\ntypeof(a)\n#> [1] "any"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

19:VECSXP

\n

就是大家熟悉的万能Rlist

\n
l <- list()\ntypeof(l)\n#> [1] "list"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

20:EXPRSXP

\n

用于未计算的表达式(列表)

\n
e <- expression(hello * world)\ntypeof(e)\n#> [1] "expression"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

21:BCODEXXP

\n

用于编译函数的字节代码。

\n
b <- .Internal(bodyCode(mean))\ntypeof(b)\n#> [1] "bytecode"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

22:EXTPTRSXP

\n

这已经在问题中提到过,在这里是为了完整性

\n
e <- new("externalptr")\ntypeof(e)\n# [1] "externalptr"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

23:弱引用XP

\n

这已经在问题中提到过,在这里是为了完整性

\n
w <- rlang::new_weakref(.GlobalEnv)\ntypeof(w)\n#> [1] "weakref"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

24:RAWXP

\n

这只是一个无符号 8 位整数数组

\n
r <- as.raw(1L)\ntypeof(r)\n#> [1] "raw"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

25:S4SXP

\n

这用于在本机面向对象的 S4 系统中创建的对象

\n
setClass("R_obj", slots = c(a = "character", b = "numeric"))\ns <- new("R_obj", a = "Hello world", b = 1)\ntypeof(s)\n#> [1] "S4"\n
Run Code Online (Sandbox Code Playgroud)\n
\n

除了这 24 种类型之外,还定义了另外 3 种 SEXPTYPE,它们在类型查找表中没有名称,因此无法从typeof. 它们是 30 (NEWSXP)、31 (FREESXP) 和 99 (FUNSXP)。前两个在内部用于内存管理/垃圾收集,并且应该只存在微秒,第三个用作占位符 SEXPTYPE,用于在搜索模式函数的对象时将闭包/内置函数/特殊函数集中在一起。据我所知,没有任何 SEXP 实际上具有这种 SEXTYPE。

\n

我很想知道是否有人有办法在不使用 rlang / Rcpp 的情况下创建 PROMSXP 或 WEAKREFSXP。如果能听到有关在不使用编译代码的情况下创建 CHARSXP 或 ANYSXP 的任何方法(尽管这些在控制台中使用时似乎有点不稳定,但它们是生成的),那也是件好事。

\n
\n

最后一点,在讨论类型时会出现密切相关的模式存储模式类概念。modestorage.mode本质上都是类型的别名,如下所述:

\n

存储方式

\n

调用storage.mode(x)只是调用typeof(x)并返回它,除非typeof(x)是“closure”、“builtin”或“special”,然后storage.mode返回“function”。因此,它只是类型的轻微抽象/简化。

\n

模式

\n

该调用mode(x)还将闭包/特殊/内置函数调用typeof(x)并简化为单一模式“函数”。此外,它对于整数和实数类型都返回“numeric”。它将“符号”更改为“名称”,并将“语言”更改为“调用”或“(”,具体取决于语言对象是否以括号开头。

\n

此图给出了类型、存储模式和模式之间的完整映射:\n在此输入图像描述

\n

班级

\n

你可以每天使用 R,而不需要了解任何有关类型、模式和存储模式的信息,但是一个称职的 R 用户需要了解的概念。它是一个对象的类,它确定在调用泛型函数时分派哪些方法,因此它是控制对象行为的类。

\n

您可以通过设置对象的类属性来设置对象的类:

\n
x <- 1\nclass(x) <- "foo"\nclass(x)\n#> [1] "foo"\n
Run Code Online (Sandbox Code Playgroud)\n

然而, R 中的每个对象都有一个类,即使是没有类属性的对象:

\n
x <- 1:5\nclass(x)\n#> [1] integer\n
Run Code Online (Sandbox Code Playgroud)\n

这是因为 R 通过 C 函数do_data_class确定对象的类。如果有一个“class”属性集,那么就是类。如果没有设置“类”属性,则首先 R 将检查维度属性。如果存在非零维度属性,则该类将是一个“数组”(尽管如果它恰好具有二维,则它将具有 class c("matrix", "array"))。如果没有类维度属性,则检索对象的类型。根据类型,R 将返回:

\n
    \n
  • "function"用于内置或特殊的封闭件
  • \n
  • "numeric"REALSXP(尽管令人惊讶地不是INTSXP
  • \n
  • "name"对于符号
  • \n
  • 对于语言对象,如果第一个符号是ifwhilefor=<-({则将返回该符号。否则"call"返回。这是一个相当神秘的系统,它似乎是处理语言语法不同元素的一种方式。
  • \n
  • 在所有其他情况下,该类typeof对象。
  • \n
\n
\n

总之,类型是存储在内存中的对象的实际类型,而模式是实际类型的部分抽象,它为我们提供了我们通常认为是 R 中“基本类型”的熟悉名称。存储模式是用途有限的类型。类是 R 中最熟悉、最有用的数据类型抽象,如果一个对象没有指定的类,R 将为它分配一个隐式类根据上述规则

\n

  • 哇,艾伦的回答又快又惊人。不幸的是,我自己没有能力接受它,但似乎@jakub 很活跃,所以希望他能看到这一点。一些简化,对于语言,您可能只需执行`quote(hello * world)`,对于点,不需要嵌套函数调用,我们可以这样做:`(function(...) get("...") )(a = 1)` (4认同)
  • @moodymudskipper 谢谢。这是你发现的一个有趣的问题,我并不担心答案被接受——这只是一个发布答案的好机会,它可以揭开 R 的一些内部工作原理。我想知道如何构建答案,我想最好涵盖所有类型,无论是否具有异国情调,因为非异国情调的类型很容易。我使用了“str2lang”来实现多样性,但会改进dotxp代码:你是点的大师! (3认同)
  • 谢谢你们回到这个话题,伙计们! (2认同)
  • 我不介意,我想我会在上面说,对象的隐式类通常与类型相同,并且异常在相关类型描述中描述,隐式类的定义以及“foo”示例、“do_data_class”等可能超出范围。图表上可能还有用于隐式类的第四列的空间。矩阵/数组的情况使事情变得有点复杂...... (2认同)

use*_*330 6

Promise 最常在调用函数时创建:您指定的参数成为 Promise。然而,很难(不可能?)返回typeof“promise”,因为 R 代码会导致promise 被求值,并且它会变成不同的类型。您可以将其视为 C 代码中的承诺,但在 R 中则不然。

“char”是字符向量中单个条目的类型。你永远无法在 R 代码中看到这一点。

“...”是参数中传递的函数的参数列表...。这有点像一个承诺;当你看着它时,你通常会强迫它变成别的东西。然而,@moodymudskipper 指出environment()$...返回实际...对象。

“any”在 S4 系统中以某种方式使用;我不知道你是否可以创建一个赤裸裸的例子。

“字节码”是函数的编译版本。一旦函数被编译(包中的大多数函数都将被编译),您可以使用获取其字节码.Internal(bodyCode(fn)),例如

x <- .Internal(bodyCode(mean))
typeof(x)
#> [1] "bytecode"
compiler::disassemble(x)
#> list(.Code, list(11L, CALLSPECIAL.OP, 0L, RETURN.OP), list(UseMethod("mean"), 
#>     structure(c(NA, 0L, 0L, 0L), class = "expressionsIndex")))
Run Code Online (Sandbox Code Playgroud)