library(rlang)
library(purrr)
#>
#> Attaching package: 'purrr'
#> The following objects are masked from 'package:rlang':
#>
#> %@%, flatten, flatten_chr, flatten_dbl, flatten_int,
#> flatten_lgl, flatten_raw, invoke, splice
19 Quasiquotation
Introduction
准引用(quasiquotation)包括两部分——引用和解引用,引用就是我们上章介绍过的捕获“表达式”,解引用相当于“评估”“表达式”。但是要注意,解引用函数只在引用函数内有效,它的目的是“评估”要捕获的“表达式”中的部分元素。准引用使得组合“函数创建者的函数”与“函数使用者的函数”更加容易。
准引用是组成tidy-evaluation的三大基石之一(quosures和data-mask将在下章介绍),在tidy-evaluation中,所有支持引用的函数都支持解引用。
Outline
- 19.2节:通过一个示例函数——
cement()
介绍为什么使用准引用。 - 19.3节:介绍引用函数。
- 19.4节:介绍解引用函数。
- 19.5节:讨论base R中类似的“解引用”。
- 19.6节:介绍使用
!!!
的另外两种情况。 - 19.7节:介绍一些示例。
Prerequisites
需要熟悉第17,18章的内容。
Motivation
我们将从一个具体的例子开始,激发准引用的使用需求。假设你要创建一个类似paste()
的函数:
paste("Good", "morning", "Hadley")
#> [1] "Good morning Hadley"
paste("Good", "afternoon", "Alice")
#> [1] "Good afternoon Alice"
如果你厌倦了每次都输入引号““”,那么你可以创建下面的函数(后续会详细介绍如何使用):
<- function(...) {
cement <- ensyms(...)
args paste(purrr::map(args, as_string), collapse = " ")
}
cement(Good, morning, Hadley)
#> [1] "Good morning Hadley"
cement(Good, afternoon, Alice)
#> [1] "Good afternoon Alice"
上面的函数使得我们无需在每个字符两边添加引号,但它有个问题是:当我们使用变量指代字符时,它无法识别变量。paste()
函数则可以正常识别
<- "Hadley"
name <- "morning"
time
cement(Good, time, name)
#> [1] "Good time name"
paste("Good", time, name)
#> [1] "Good morning Hadley"
我们可以使用特殊的解引符号!!
处理上面这种情况:
cement(Good, !!time, !!name)
#> [1] "Good morning Hadley"
上面的示例可能具有误导性。前面我们讲到过,常量的“表达式”就是常量,所以"Hadley"
等价于expr("Hadley")
,!!
始终作用的是一个“表达式”,并且它会被传入到ensyms()
中,也即解引函数始终在引用函数内生效。
引用函数ensyms()
的返回值是一个表达式,我们可以对他进行“评估”或as_string()
,但这些都已经不属于准引用的范畴了。
Vocabulary
“引用”与“评估”参数之间的不同是重要的:
- 一个被评估的参数遵循R的常规评估规则。
- 一个被引用的参数被保存为表达式,遵循自定义的处理规则。
上面的paste()
的参数被评估,cement()
的参数被引用。
如果你不确定一个参数是被评估还是被引用,你可以在函数外面的环境中允许这个参数,如果报错,那么这个参数被引用。例如:
# works
library(MASS)
# fails
MASS#> Error: object 'MASS' not found
Quoting
准引用的第一个部分是引用——捕获表达式但不进行评估。引用相关的函数通常是成对出现,因为要考虑直接引用和间接引用(函数中的惰性评估)。本节首先介绍“rlang”中的引用函数,然后介绍base R中的。
Capturing expressions
捕获表达式的函数有四个:
单个引用 | 多个引用 | |
---|---|---|
交互场景 | expr() |
exprs() |
函数场景 | enexpr() |
enexprs() |
在交互场景下,expr()
和exprs()
都直接捕获它们的参数,前者捕获单个参数,后者捕获多个参数。
expr(x + y)
#> x + y
expr(1 / 2 / 3)
#> 1/2/3
exprs(x = x^2, y = y^3, z = z^4)
#> $x
#> x^2
#>
#> $y
#> y^3
#>
#> $z
#> z^4
# shorthand for
# list(x = expr(x ^ 2), y = expr(y ^ 3), z = expr(z ^ 4))
在函数场景下,因为惰性评估的原因,需要使用额外的函数enexpr()
和enexprs()
。
# expr 直接捕获参数
<- function(x) expr(x)
f1 f1(a + b + c)
#> x
# exprs 捕获函数的参数评估后的结果
<- function(x) enexpr(x)
f2 f2(a + b + c)
#> a + b + c
捕获特殊参数...
的结果只能使用enexprs()
。
<- function(...) enexprs(...)
f f(x = 1, y = 10 * z)
#> $x
#> [1] 1
#>
#> $y
#> 10 * z
Capturing symbols
ensym()
和ensyms()
函数是enexpr()
和enexprs()
的变体,专门用于捕获符号或将字符串转换为符号,输入不符合时会报错。
<- function(...) ensyms(...)
f f(x)
#> [[1]]
#> x
f("x")
#> [[1]]
#> x
With base R
上述讲到的函数在base R中都有对应的等价函数,它们之间的主要不同是base R中的函数不支持解引用。
base R | rlang |
---|---|
quote() |
expr() |
alist() |
exprs() |
substitute() |
enexpr() |
as.list(substitute(...())) |
enexprs() |
quote(x + y)
#> x + y
alist(x = 1, y = x + 2)
#> $x
#> [1] 1
#>
#> $y
#> x + 2
<- function(x) substitute(x)
f3 f3(x + y)
#> x + y
<- function(...) as.list(substitute(...()))
f f(x = 1, y = 10 * z)
#> $x
#> [1] 1
#>
#> $y
#> 10 * z
除此之外,base R中还有两个重要的引用函数:
bquote()
:提供了有限的准引用形式,将在19.5节中讨论。~
:能够捕获环境的引用函数,将在20.3.4中讨论。
Substitution
substitute()
函数除了执行“引用”的功能外,还执行“替换”的功能。
<- function(x) substitute(x * 2)
f4 f4(a + b + c)
#> (a + b + c) * 2
如果你在交互场景中使用它的“替换”功能时,推荐添加第二个参数来指定哪些是要替换的。
substitute(x * y * z, list(x = 10, y = quote(a + b)))
#> 10 * (a + b) * z
Summary
在使用引用功能时,你始终要注意两点:
要引用的对象是固定(交互场景)还是不固定的(函数场景)。
要引用单个还是多个。
Developer | User | |
---|---|---|
One | expr() |
enexpr() |
Many | exprs() |
enexprs() |
Developer | User | |
---|---|---|
One | quote() |
substitute() |
Many | alist() |
as.list(substitute(...())) |
Unquoting
解引用允你许选择性地评估“表达式”中的部分内容,其余仍然被引用。这样,你可以使用一个AST模板去生成其他AST。base R需要使用另外的技术来实现,我们将在19.5中介绍。
与解引用类似的是eval()
函数(20章),但他们是不同的,解引用在引用函数内使用——expr(!!x)
,评估实在函数外使用——eval(expr(x))
,它们最终的结果也可能不同。
Unquoting one argument
!!
会“评估”一个“表达式”,并返回的值插入到整体的“表达式”中:
<- expr(-1)
x expr(f(!!x, y))
#> f(-1, y)
下面是!!
运行的底层逻辑示例图:
!!
当然也可以作用于符号或常量:
<- sym("y")
a <- 1
b expr(f(!!a, !!b))
#> f(y, 1)
!!
作用于返回“表达式”的函数:
<- function(var) {
mean_rm <- ensym(var)
var expr(mean(!!var, na.rm = TRUE))
}expr(!!mean_rm(x) + !!mean_rm(y))
#> mean(x, na.rm = TRUE) + mean(y, na.rm = TRUE)
!!
会保留操作符的优先级:
<- expr(x + 1)
x1 <- expr(x + 2)
x2
expr(!!x1 / !!x2)
#> (x + 1)/(x + 2)
如果我们只是简单地将表达式的文本粘贴在一起,我们最终会得到x + 1 / x + 2
,这是完全不同的AST:
Unquoting a function
通常!!
用来解引用参数,但是!!
也可以用来解引用函数。需要注意的是expr(!!f(x))
解引用的是f(x)
,需要使用额外的括号表示解引函数——expr((!!f)(x))
。
<- expr(foo)
f expr((!!f)(x, y))
#> foo(x, y)
函数f
来自于包的写法也可以:
<- expr(pkg::foo)
f expr((!!f)(x, y))
#> pkg::foo(x, y)
上面的代码也可以使用rlang::call2()
改写:
<- expr(pkg::foo)
f call2(f, expr(x), expr(y))
#> pkg::foo(x, y)
Unquoting a missing argument
在极少数情况下,我们需要解引用缺失值参数(missing argument),但直接解引用会失效:
<- missing_arg()
arg expr(foo(!!arg, !!arg))
#> Error: argument "arg" is missing, with no default
我们需要使用rlang::maybe_missing()
来处理缺失值参数:
expr(foo(!!maybe_missing(arg), !!maybe_missing(arg)))
#> foo(, )
Unquoting in special forms
某些函数的infix形式的解引用会失败,例如$
:它必须始终跟随变量名称,而不是其他表达式,强制使用会报错:
<- expr(x)
x expr(df$!!x)
#> Error in parse(text = input): <text>:2:9: unexpected '!'
#> 1: x <- expr(x)
#> 2: expr(df$!
#> ^
需要将函数转变为prefix形式:
<- expr(x)
x expr(`$`(df, !!x))
#> df$x
Unquoting many arguments
!!
是一对一地解引用,!!!
是一对多的解引用。可以是“表达式”列表或带name属性的向量和列表。
<- exprs(1, a, -b)
xs expr(f(!!!xs, y))
#> f(1, a, -b, y)
# Or with names
<- set_names(xs, c("a", "b", "c"))
ys expr(f(!!!ys, d = 4))
#> f(a = 1, b = a, c = -b, d = 4)
!!!
也可以应用在call2()
中:
call2("f", !!!xs, expr(y))
#> f(1, a, -b, y)
The polite fiction of !!
!!
和!!!
与R中的类似+
,-
,!
等运算符不同,在R的视角里,!!
就是运行了两次的!
:
!!TRUE
#> [1] TRUE
!!!TRUE
#> [1] FALSE
!!
和!!!
必须在准引用函数中才能变得类似+
,-
,!
等运算符。在准引用函数外使用,会被视为!
作用于“表达式”,导致报错:
<- quote(variable)
x !!x
#> Error in !x: invalid argument type
但是因为!
可以作用于数字,所以有时会产生错误的结果:
<- data.frame(x = 1:5)
df <- 100
y with(df, x + !!y)
#> [1] 2 3 4 5 6
Non-standard ASTs
在解引用时,可能会轻易地创建出非标准的AST,例如解引用的对象不是“表达式”时。这些非标准AST是有效的,偶尔也很有用,但它们的正确使用超出了本书的范围。然而,了解它们很重要,因为它们可能会被解析,从而以误导性的方式被打印出来。
例如,如果你内联更复杂的对象,它们的属性就不会打印出来。这可能会导致输出混乱:
<- expr(class(!!data.frame(x = 10)))
x1
x1#> class(list(x = 10))
eval(x1)
#> [1] "data.frame"
有两个工具可以消除这种混乱:rlang::expr_print()
和lobstr::ast()
:
expr_print(x1)
#> class(<df[,1]>)
::ast(!!x1)
lobstr#> █─class
#> └─<inline data.frame>
另外一种插入整数语句造成地混乱:
<- expr(f(!!c(1L, 2L, 3L, 4L, 5L)))
x2
x2#> f(1:5)
expr_print(x2)
#> f(<int: 1L, 2L, 3L, 4L, 5L>)
::ast(!!x2)
lobstr#> █─f
#> └─<inline integer>
也可以创建由于运算符优先级而无法从代码中生成的常规AST。在这种情况下,R将打印AST中不存在的括号:
<- expr(1 + !!expr(2 + 3))
x3
x3#> 1 + (2 + 3)
::ast(!!x3)
lobstr#> █─`+`
#> ├─1
#> └─█─`+`
#> ├─2
#> └─3
Non-quoting
base R中的bquote()
函数支持准引用,使用.()
来解引用。
<- bquote(x + y + z)
xyz bquote(-.(xyz) / 2)
#> -(x + y + z)/2
但bquote()
函数在base R中并没有被广泛使用,也没有对R的书写方式产生任何轻微的影响。主要有三个原因:
- 它无法将创作者的函数和使用者的函数组合在一起,只适合自己使用。
- 它不支持解引用多个表达式。
- 它不支持提供的环境及在环境中处理代码。
base R中具有引用参数功能的函数使用的其他技术路径(不是bquote()
),在需要“解引用”时,它们选择性地关闭引用功能,不是真正的“解引用”,这里称之为非引用。
引用与非引用在base R中有四种基本形式:
- 成对出现的引用和非引用函数。例如,
$
有两个参数,第二个参数引用的。把mtcars$cyl
写成prefix型式`$`(mtcars, cyl)
,就会很容易发现它的引用特性。更直接的对比是非引用函数[[
:
<- list(var = 1, y = 2)
x <- "y"
var
$var
x#> [1] 1
x[[var]]#> [1] 2
base R中还有三个与$
紧密相关的函数:subset()
、transform()
和with()
。它们被视为仅适用于交互场景使用的$
包装,因此都具有相同的非引用替代函数:[<-
/assign()
、::
/getExportedValue()
。
- 成对出现的引用和非引用参数。例如,
rm()
函数允许在...
中使用引用参数或在list
中提供非引用参数。同样的还有data()
,save()
等。
<- 1
x rm(x)
<- 2
y <- c("y", "vars")
vars rm(list = vars)
- 通过某个参数控制另外一个参数是否是引用的。例如,
library()
函数的character.only
参数控制package
参数是否是引用的。同样的还有demo()
,detach()
,example()
,require()
。
library(MASS)
<- "MASS"
pkg library(pkg, character.only = TRUE)
- 进行尝试,如果评估失败尝试引用。例如,
help()
函数的第一个参数要求是非引用的字符串,如果评估失败会尝试引用参数。同样的还有ls()
,page()
,match.fun()
。
# Shows help for var
help(var)
#> No documentation for 'y' in specified packages and libraries:
#> you could try '??y'
<- "mean"
var # Shows help for mean
help(var)
#> starting httpd help server ... done
<- 10
var # Shows help for var
help(var)
base R中另一类重要的引用函数是建模和绘图函数,它们遵循所谓的标准非标准评估规则。例如,lm()
函数的formula
参数,绘图函数plot()
中的映射类参数col
,cex
,pch
等等。
palette(RColorBrewer::brewer.pal(3, "Set1"))
plot(
~ Petal.Length,
Sepal.Length data = iris,
col = Species,
pch = 20,
cex = 2
)
...
(dot-dot-dot)
本节介绍两种常见的使用!!!
和:=
的场景。
- 使用
!!!
解引用参数,释放到...
中。考虑下面的数据框列表,如果要按行合并它们,你可以直接使用rbind(dfs$a, dfs$b)
,但如果list中包含多个数据框呢,或者数量未知呢?
<- list(
dfs a = data.frame(x = 1, y = 2),
b = data.frame(x = 3, y = 4)
)
可以使用!!!
直接解引用参数,释放到...
中。
::bind_rows(!!!dfs)
dplyr#> x y
#> 1 1 2
#> 2 3 4
在这种情况下使用时,!!!
的行为在Ruby、Go、PHP和Julia中被称为“splatting”。它与Python中的*args(star-args)
和*kwarg(star-star-kwargs)
密切相关,这些行为有时被称为参数解包。
- 如何自定义符号的名字。例如,你想创建一个单列的数据框,并且它的列名由变量
var
决定,你可以使用setNames(data.frame(val), var)
,但这样并不“优雅”。
<- "x"
var <- c(4, 3, 9) val
可以使用:=
可以在=
的左边解引用参数:
::tibble(!!var := val)
tibble#> # A tibble: 3 × 1
#> x
#> <dbl>
#> 1 4
#> 2 3
#> 3 9
因为R不支持使用“表达式”作为参数名称,所以使用了新的操作符:=
:
::tibble(!!var = val)
tibble#> Error in parse(text = input): <text>:1:22: unexpected '='
#> 1: tibble::tibble(!!var =
#> ^
作者称支持这些无需引用参数工具的函数,都有tidy dots。创建具有tidy dots的函数只需要使用list2()
。
Examples
下面是一个设置属性函数的例子,它允许我们灵活的设置属性。
<- function(.x, ...) {
set_attr <- rlang::list2(...)
attr attributes(.x) <- attr
.x
}
<- list(x = 1, y = 2)
attrs <- "z"
attr_name
1:10 %>%
set_attr(w = 0, !!!attrs, !!attr_name := 3) %>%
str()
#> int [1:10] 1 2 3 4 5 6 7 8 9 10
#> - attr(*, "w")= num 0
#> - attr(*, "x")= num 1
#> - attr(*, "y")= num 2
#> - attr(*, "z")= num 3
exec()
如果你想再没有tidy-dots的函数中使用!!!
和:=
,你可以使用exec()
。它的使用方法与call2()
类似,但call2()
返回“表达式”,而exec()
直接评估了“表达式”。
使用!!!
解析参数列表:
# Directly
exec("mean", x = 1:10, na.rm = TRUE, trim = 0.1)
#> [1] 5.5
# Indirectly
<- list(x = 1:10, na.rm = TRUE, trim = 0.1)
args exec("mean", !!!args)
#> [1] 5.5
# Mixed
<- list(na.rm = TRUE, trim = 0.1)
params exec("mean", x = 1:10, !!!params)
#> [1] 5.5
使用:=
解析不固定参数:
<- "na.rm"
arg_name <- TRUE
arg_val exec("mean", 1:10, !!arg_name := arg_val)
#> [1] 5.5
exec()
也可以和泛函配合使用:
<- c(runif(10), NA)
x <- c("mean", "median", "sd")
funs
::map_dbl(funs, exec, x, na.rm = TRUE)
purrr#> [1] 0.6283860 0.7322721 0.3160886
dots_list()
list2()
提供了一个方便的功能:它会自动忽略最后一个空参数。这意味你可以在诸如tibble::tibble()
这样的函数中不必理会最后一个元素后的逗号。
# Can easily move x to first entry:
::tibble(
tibbley = 1:5,
z = 3:-1,
x = 5:1,
%>%
) ::mutate(
dplyra = 1:5,
b = 5:1,
c = 1:5,
)#> # A tibble: 5 × 6
#> y z x a b c
#> <int> <int> <int> <int> <int> <int>
#> 1 1 3 5 1 5 1
#> 2 2 2 4 2 4 2
#> 3 3 1 3 3 3 3
#> 4 4 0 2 4 2 4
#> 5 5 -1 1 5 1 5
# Need to remove comma from z and add comma to x
data.frame(
y = 1:5,
z = 3:-1,
x = 5:1
)#> y z x
#> 1 1 3 5
#> 2 2 2 4
#> 3 3 1 3
#> 4 4 0 2
#> 5 5 -1 1
list2()
函数是对rlang::dots_list()
函数的封装,设置了一些常用设置为默认值。你可以直接使用dots_list()
函数进行个性化的设置。
.ignore_empty
参数控制忽略哪些参数。默认情况下,设置ignore_empty = "trailing"
会忽略最后一个空参数,这会导致上述行为,但你可以选择忽略所有缺失的参数("all"
),或者不忽略任何缺失的参数("none"
)。.homonyms
参数控制同名参数的处理。
str(dots_list(x = 1, x = 2))
#> List of 2
#> $ x: num 1
#> $ x: num 2
str(dots_list(x = 1, x = 2, .homonyms = "first"))
#> List of 1
#> $ x: num 1
str(dots_list(x = 1, x = 2, .homonyms = "last"))
#> List of 1
#> $ x: num 2
str(dots_list(x = 1, x = 2, .homonyms = "error"))
#> Error:
#> ! Arguments in `...` must have unique names.
#> ✖ Multiple arguments named `x` at positions 1 and 2.
- 如果存在未被忽略的空参数,
.preserve_empty
控制如何处理它们。默认情况下会抛出一个错误;设置.preserve_empty = TRUE
则会返回缺失符号。如果使用dots_list()
生成函数调用,这将很有用。
With base R
base R 提供了一个瑞士军刀般的函数——do.call()
。该函数接受两个参数,首个参数what
接受一个函数名,第二个参数args
接受一个参数列表。例如,do.call("f", list(x, y ,z))
等价于f(x, y, z)
。
do.call()
解决rbind()
多个数据框:
do.call("rbind", dfs)
#> x y
#> a 1 2
#> b 3 4
do.call()
解决变量名的问题:
<- list(val)
args names(args) <- var
do.call("data.frame", args)
#> x
#> 1 4
#> 2 3
#> 3 9
一些base R中的函数(interaction()
, expand.grid()
, options()
, par()
)使用了一种技巧去规避使用do.call()
:如果...
中的第一个参数是列表,则直接使用这个列表,跳过...
中的剩余参数。
<- function(...) {
f <- list(...)
dots if (length(dots) == 1 && is.list(dots[[1]])) {
<- dots[[1]]
dots
}
# Do something
... }
RCurl::getURL()
函数采取了另外一种技巧:同时处理...
和.dots
。
<- function(..., .dots) {
f <- c(list(...), .dots)
dots # Do something
}
Case studies
为了使准引用的概念具体化,本节包含一些用它来解决实际问题的小案例,这些案例还使用了purrr
。
lobstr::ast()
准引用使得lobstr::ast()
的输入可以是一个“表达式”。
<- expr(foo(x, y))
z ::ast(z)
lobstr#> z
::ast(!!z)
lobstr#> █─foo
#> ├─x
#> └─y
Map-reduce to generate code
准引用赋予了我们强大的“生成”代码的能力,尤其是与purrr::map()
及purrr::reduce()
结合使用时。例如,假设你有一个由以下系数指定的线性模型,你需要将其转换为一个函数式——10 + (x1 * 5) + (x2 * -4)
。
<- 10
intercept <- c(x1 = 5, x2 = -4) coefs
你可以遵循以下步骤构建函数式:
- 首先使用
rlang::syms()
将名称x1
,x2
转换为符号列表。
<- syms(names(coefs))
coef_sym
coef_sym#> [[1]]
#> x1
#>
#> [[2]]
#> x2
- 然后使用
rlang::expr()
和purrr::map2()
将系数与对应的未知数组合。
<- map2(coef_sym, coefs, ~ expr((!!.x * !!.y)))
summands
summands#> [[1]]
#> (x1 * 5)
#>
#> [[2]]
#> (x2 * -4)
- 最后使用
purrr::reduce()
将所有项组合成一个表达式。
reduce(c(intercept, summands), ~ expr(!!.x + !!.y))
#> 10 + (x1 * 5) + (x2 * -4)
Slicing an array
base R中缺少一个根据给定维度和索引从数组中提取切片的函数。例如,编写函数slice(x, 2, 1)
即x[, 1, ]
,实现沿着第二个维度提取第一个切片。这是一个略具挑战性的问题,因为它要处理缺失的参数。
我们采取的策略是首先默认全都是缺失的参数,然后根据提供的维度和索引,替换缺失的参数。
<- rep(list(missing_arg()), 3)
indices expr(x[!!!indices])
#> x[, , ]
2]] <- 1
indices[[expr(x[!!!indices])
#> x[, 1, ]
我们可以进一步优化为一个函数:
<- function(x, along, index) {
slice stopifnot(length(along) == 1)
stopifnot(length(index) == 1)
<- length(dim(x))
nd <- rep(list(missing_arg()), nd)
indices <- index
indices[[along]]
expr(x[!!!indices])
}
<- array(sample(30), c(5, 2, 3))
x slice(x, 1, 3)
#> x[3, , ]
slice(x, 2, 2)
#> x[, 2, ]
slice(x, 3, 1)
#> x[, , 1]
一个真实的slice()
函数会在第20章中介绍。
Creating functions
另外一个有用的应用是使用rlang::new_function()
“手动”创建函数,创建函数的三要素——参数,主体,环境。
new_function(
exprs(x = , y = ),
expr({
+ y
x
})
)#> function (x, y)
#> {
#> x + y
#> }
注意:exprs()
中的空参数产生没有默认值的函数参数。
new_function()
一个用法是作为函数工厂的替代,例如之前讲到的幂函数:
<- function(exponent) {
power new_function(
exprs(x = ),
expr({
^!!exponent
x
}),caller_env()
)
}power(0.5)
#> function (x)
#> {
#> x^0.5
#> }
new_function()
的另一个用法是用于类似graphics::curve()
这样的函数,它能够绘制数学表达式,而无需创建函数。
curve(sin(exp(4 * x)), n = 1000)
在这段代码中,x
是一个代词:它不表示单一的具体值,而是一个在图形范围内变化的占位符。实现curve()
的一种方法是将该表达式转换为一个带有单个参数x
的函数,然后调用该函数。
<- function(expr, xlim = c(0, 1), n = 100) {
curve2 <- enexpr(expr)
expr <- new_function(exprs(x = ), expr)
f
<- seq(xlim[1], xlim[2], length = n)
x <- f(x)
y
plot(x, y, type = "l", ylab = expr_text(expr))
}curve2(sin(exp(4 * x)), n = 1000)
<- new_function(exprs(x = ), expr(sin(exp(4 * x)))) f
像curve()
这样使用包含代词的表达式的函数被称为指代函数(anaphoric functions)。