library(lobstr)
对象的变量名是一个常量指针,指向一个对象所在的地址。
<- 1:3
x <- x # x, y 为指向同一个向量的指针
y <- 1:3
z
obj_addr(x) # 对象的唯一标识符,并非内存中真正的地址
#> [1] "0x1daa7956720"
obj_addr(y)
#> [1] "0x1daa7956720"
obj_addr(z) # 可见,z 指向的是另一个对象
#> [1] "0x1daa79fb1d0"
# 几种访问函数的方式,第二个指向的不是同一个潜在函数对象
# 可能是因为某个包的 mean() 覆盖了 base::mean()
%>% obj_addr() mean
#> [1] "0x1daa79f6c38"
::mean %>% obj_addr() base
#> [1] "0x1da9cf7eff0"
get("mean") %>% obj_addr()
#> [1] "0x1daa79f6c38"
evalq(mean) %>% obj_addr()
#> [1] "0x1daa79f6c38"
match.fun("mean") %>% obj_addr()
#> [1] "0x1da9cf7eff0"
R 中变量的命名规则:名称只能包含 a~z
,
A~Z
,
下划线_
和句点.
,不能有空格,也不能以下划线_
开头,而且不能是
R 中的 reserved word
违反命名规则的变量名,要用反引号括起来,才能被识别
%>% class() # c是一个内置函数,所以一般变量不能以'c'命名 c
#> [1] "function"
c
#> function (...) .Primitive("c")
`x+1` <- function(n) n + 1
`x+1`
#> function(n) n + 1
`x+1`(3)
#> [1] 4
R 中的对象一般不允许被修改(就像 JS
中的const
常量),这对数据安全有好处。
一旦涉及数据修改,R 常常会 copy 一份数据的副本,然后在副本上修改数据——这是 R 速度慢的一个重要原因。
特别是 for 循环,每次迭代都会对数据产生一次或多次拷贝。purrr 包的高阶函数比较快就是因为避免了这一点。
有多个指针指向同一个对象,且通过其中一个指针修改该对象时,会生成该对象的一个副本(一个新对象)。修改作用在这个副本上,然后使被修改的指针指向这个副本。
<- c(1, 2, 3)
x cat(tracemem(x), "\n") # tracemen()跟踪指针指向的对象,每当这个对象被复制,会打印一条说明
#> <000001DAAE36C0B8>
<- x
y 3] <- 4L # x, y 均指向对象,通过 y 修改对象时,发生 copy y[
#> tracemem[0x000001daae36c0b8 -> 0x000001daae3843f8]: eval eval eval_with_user_handlers withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir in_input_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
obj_addr(x)
#> [1] "0x1daae36c0b8"
obj_addr(y)
#> [1] "0x1daae3843f8"
3] <- 5L # 不会 copy,因为只有唯一指针 y 指向对象
y[obj_addr(y)
#> [1] "0x1daae3843f8"
untracemem(y) # 关闭跟踪
对象作为参数传递给函数,并在函数内部修改对象时,也会创建对象的副本。函数外部的变量名仍指向未被修改的原对象
<- c(1, 2, 3)
v1 cat(tracemem(v1), "\n")
#> <000001DAAE624AE8>
<- function(x) {
modify1 1] <- 0
x[
x
}modify1(v1) %>% obj_addr()
#> tracemem[0x000001daae624ae8 -> 0x000001daae640e48]: modify1 obj_addr_ obj_addr %>% eval eval eval_with_user_handlers withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir in_input_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
#> [1] "0x1daae640e48"
# 外部的 v1 未被修改 v1
#> [1] 1 2 3
tracemem(v1)
#> [1] "<000001DAAE624AE8>"
为了节省空间,列表储存的不是对象,而是指针。修改列表时,只有变化的元素会复制。
<- list(1, 2, 3)
l1 <- l1
l2 3]] <- 4 # 拷贝机制启动,但拷贝的是指针,而不是 value,这是浅拷贝
l2[[::ref(l1, l2) lobstr
#> █ [1:0x1daa6c99b68] <list>
#> ├─[2:0x1daa56f8ce0] <dbl>
#> ├─[3:0x1daa56f8d18] <dbl>
#> └─[4:0x1daa56f8d50] <dbl>
#>
#> █ [5:0x1daa6db2de8] <list>
#> ├─[2:0x1daa56f8ce0]
#> ├─[3:0x1daa56f8d18]
#> └─[6:0x1daa56f8e30] <dbl>
<- 1:10
a <- list(a, a)
b <- list(b, a, 1:10)
c ::ref(a) lobstr
#> [1:0x1daa2978200] <int>
# 第一行是指向列表的指针,2、3行是指向列表元素的指针
# b的两个元素,指向同一个向量
::ref(b) lobstr
#> █ [1:0x1daab074838] <list>
#> ├─[2:0x1daa2978200] <int>
#> └─[2:0x1daa2978200]
::ref(c) # c的第三个元素不同于a指向的向量 lobstr
#> █ [1:0x1daa9c9cbf8] <list>
#> ├─█ [2:0x1daab074838] <list>
#> │ ├─[3:0x1daa2978200] <int>
#> │ └─[3:0x1daa2978200]
#> ├─[3:0x1daa2978200]
#> └─[4:0x1daa2724f90] <int>
<- list(1:10)
x ref(x)
#> █ [1:0x1daa5fa5d68] <list>
#> └─[2:0x1daa57534c8] <int>
2]] <- x
x[[ref(x)
#> █ [1:0x1daaca3e858] <list>
#> ├─[2:0x1daa57534c8] <int>
#> └─█ [3:0x1daa5fa5d68] <list>
#> └─[2:0x1daa57534c8]
数据框本质上是一个列表,储存的都是指向列向量的指针。
<- data.frame(x = c(1, 5, 6), y = c(2, 4, 3))
d1 <- d1
d2 <- d1
d3 ref(d1, d2, d3)
#> █ [1:0x1daa7c31cd8] <df[,2]>
#> ├─x = [2:0x1daad9f05c8] <dbl>
#> └─y = [3:0x1daad9f0578] <dbl>
#>
#> [1:0x1daa7c31cd8]
#>
#> [1:0x1daa7c31cd8]
# 修改列,该列复制;其他列扔指向原对象
2] <- d2[, 2] * 2
d2[, ref(d1, d2)
#> █ [1:0x1daa7c31cd8] <df[,2]>
#> ├─x = [2:0x1daad9f05c8] <dbl>
#> └─y = [3:0x1daad9f0578] <dbl>
#>
#> █ [4:0x1daa7c518d8] <df[,2]>
#> ├─x = [2:0x1daad9f05c8]
#> └─y = [5:0x1daadb21b68] <dbl>
# 修改行,必须复制每一列
1, ] <- d3[1, ] * 3
d3[ref(d1, d3)
#> █ [1:0x1daa7c31cd8] <df[,2]>
#> ├─x = [2:0x1daad9f05c8] <dbl>
#> └─y = [3:0x1daad9f0578] <dbl>
#>
#> █ [4:0x1daad6bf358] <df[,2]>
#> ├─x = [5:0x1daadc39218] <dbl>
#> └─y = [6:0x1daadc39178] <dbl>
也是指针的向量。每个指针指向一个字符串,这些字符串位于全局字符串池中。
<- c("a", "a", "abc", "d")
x ref(x, character = TRUE)
#> █ [1:0x1daadcf9028] <chr>
#> ├─[2:0x1da9a6ebb08] <string: "a">
#> ├─[2:0x1da9a6ebb08]
#> ├─[3:0x1daa8c4ef08] <string: "abc">
#> └─[4:0x1da9a9402e8] <string: "d">
只有一个指针指向对象时,修改对象不会发生复制。
但 Rstudio 中与 Console 中不同,Rstudio 中仍要复制。本文件是在 Rstudio 中生成的,因此下面的代码段仍然含有复制。
<- c(1,2)
v obj_addr(v)
#> [1] "0x1daadbd5f08"
2]] <- 5
v[[obj_addr(v)
#> [1] "0x1daadbf5648"
但是,R 对指针数量的判断不够智能。如两个指针指向一个对象,其中一个被删除后,R 却不知道,仍然以为有两个指针。因此会产生很多不必要的对象拷贝。
总是被就地修改,不会 copy
此功能非常有用,可以实现闭包、R6类型系统,等等
zeallot 包提供的 %<-%
和 %>-%
函数
library(zeallot)
# unpack vector
c(lat, lng) %<-% c(38.061944, -122.643889)
lat
#> [1] 38.06194
lng
#> [1] -122.6439
# unpack list
<- function() {
coords_list list(38.061944, -122.643889)
}c(lat, lng) %<-% coords_list()
lat
#> [1] 38.06194
lng
#> [1] -122.6439
# unpack regression result
c(inter, slope) %<-% coef(lm(mpg ~ cyl, data = mtcars))
inter
#> [1] 37.88458
slope
#> [1] -2.87579
# unpack data.frame
c(mpg, cyl, disp, hp) %<-% mtcars[, 1:4]
head(mpg)
#> [1] 21.0 21.0 22.8 21.4 18.7 18.1
# unpack nested values
c(a, c(b, d), e) %<-% list("begin", list("middle1", "middle2"), "end")
a
#> [1] "begin"
b
#> [1] "middle1"
d
#> [1] "middle2"
e
#> [1] "end"
# unpack character string
c(ch1, ch2, ch3) %<-% "abc"
ch1
#> [1] "a"
ch2
#> [1] "b"
ch3
#> [1] "c"
# unpack Date
c(y, m, d) %<-% Sys.Date()
y
#> [1] 2022
m
#> [1] 5
d
#> [1] 2
# ... rest of
c(begin, ...middle, end) %<-% list(1, 2, 3, 4, 5)
begin
#> [1] 1
middle
#> [[1]]
#> [1] 2
#>
#> [[2]]
#> [1] 3
#>
#> [[3]]
#> [1] 4
# place holder
c(min_wt, ., ., mean_wt, ., max_wt) %<-% summary(mtcars$wt)
min_wt
#> [1] 1.513
mean_wt
#> [1] 3.21725
max_wt
#> [1] 5.424
# 向右的解构赋值符号 %->%
%>%
mtcars subset(hp > 100) %>%
aggregate(. ~ cyl, data = ., FUN = . %>% mean() %>% round(2)) %>%
transform(kpl = mpg %>% multiply_by(0.4251)) %->%
c(cyl, mpg, ...rest)
cyl
#> [1] 4 6 8