【发布时间】:2018-10-02 06:31:22
【问题描述】:
我试图了解如何简洁地实现参数捕获/解析/评估机制之类的东西,该机制通过dplyr::tibble() (FKA dplyr::data_frame()) 实现以下行为:
# `b` finds `a` in previous arg
dplyr::tibble(a=1:5, b=a+1)
## a b
## 1 2
## 2 3
## ...
# `b` can't find `a` bc it doesn't exist yet
dplyr::tibble(b=a+1, a=1:5)
## Error in eval_tidy(xs[[i]], unique_output) : object 'a' not found
对于像data.frame 和list 这样的base:: 类,这是不可能的(可能bc 参数没有按顺序解释(?)和/或可能bc 它们在父环境中被评估(?)) :
data.frame(a=1:5, b=a+1)
## Error in data.frame(a = 1:5, b = a + 1) : object 'a' not found
list(a=1:5, b=a+1)
## Error: object 'a' not found
所以我的问题是:在基础 R 中编写一个类似于 base::list() 的函数可能是一个好的策略,除了它允许 @ 987654330@ 行为类似于list2(a=1:5, b=a+1)??
我知道这是“tidyeval”所做的一部分,但我有兴趣找出使这个技巧成为可能的确切机制。而且我知道可以只说list(a <- 1:5, b <- a+1),但我正在寻找一种不使用全局分配的解决方案。
到目前为止我一直在想什么:实现所需行为的一种不优雅且不安全的方法如下 - 首先将参数解析为字符串,然后创建一个环境,添加每个元素到那个环境,把它们放到一个列表中,然后返回(建议将...解析成一个命名列表的更好方法!):
list2 <- function(...){
# (gross bc we are converting code to strings and then back again)
argstring <- as.character(match.call(expand.dots=FALSE))[2]
argstring <- gsub("^pairlist\\((.+)\\)$", "\\1", argstring)
# (terrible bc commas aren't allowed except to separate args!!!)
argstrings <- strsplit(argstring, split=", ?")[[1]]
env <- new.env()
# (icky bc all args must have names)
for (arg in argstrings){
eval(parse(text=arg), envir=env)
}
vars <- ls(env)
out <- list()
for (var in vars){
out <- c(out, list(eval(parse(text=var), envir=env)))
}
return(setNames(out, vars))
}
这允许我们推导出基本行为,但它根本不能很好地概括(参见list2() 定义中的 cmets):
list2(a=1:5, b=a+1)
## $a
## [1] 1 2 3 4 5
##
## $b
## [1] 2 3 4 5 6
我们可以引入 hack 来修复一些小问题,例如在未提供名称时生成名称,例如像这样:
# (still gross but at least we don't have to supply names for everything)
list3 <- function(...){
argstring <- as.character(match.call(expand.dots=FALSE))[2]
argstring <- gsub("^pairlist\\((.+)\\)$", "\\1", argstring)
argstrings <- strsplit(argstring, split=", ?")[[1]]
env <- new.env()
# if a name isn't supplied, create one of the form `v1`, `v2`, ...
ctr <- 0
for (arg in argstrings){
ctr <- ctr+1
if (grepl("^[a-zA-Z_] ?= ?", arg))
eval(parse(text=arg), envir=env)
else
eval(parse(text=paste0("v", ctr, "=", arg)), envir=env)
}
vars <- ls(env)
out <- list()
for (var in vars){
out <- c(out, list(eval(parse(text=var), envir=env)))
}
return(setNames(out, vars))
}
然后代替这个:
# evaluates `a+b-2`, but doesn't include in `env`
list2(a=1:5, b=a+1, a+b-2)
## $a
## [1] 1 2 3 4 5
##
## $b
## [1] 2 3 4 5 6
我们得到这个:
list3(a=1:5, b=a+1, a+b-2)
## $a
## [1] 1 2 3 4 5
##
## $b
## [1] 2 3 4 5 6
##
## $v3
## [1] 1 3 5 7 9
但感觉还是会有问题的边缘情况,即使我们用逗号、名称等解决问题。
任何人有任何想法/建议/见解/解决方案/等??
非常感谢!
【问题讨论】:
-
这不是一个答案,但一个非常简洁的相关函数是
alist。绝对值得一读帮助手册。 -
确实,这可能对解析
...args 有用——谢谢。基础 R 中有这么多奇怪的隐藏宝石:p -
是的,20 多年的各种添加可能会导致一些迷宫般的搜索。