如何将字符串向量(每个都给出命令)转换为 R 中的函数?
How do I turn a vector of character strings (each giving commands) into a function in R?
我正在尝试构建一个函数,该函数从其输入创建并输出一个新的 函数(为方便起见,我将其称为输出函数)。我能够为输出函数生成我想要的代码行作为字符串,并将其放入一个字符向量中,该向量通过函数主体中我想要的命令。但是,我无法将其转换为输出函数。
我观察到我可以使用 body
命令提取函数的主体,并将其转换为字符向量。例如,以下是将此应用于 matrix
函数时得到的结果:
body(matrix)
{
if (is.object(data) || !is.atomic(data))
data <- as.vector(data)
.Internal(matrix(data, nrow, ncol, byrow, dimnames, missing(nrow),
missing(ncol)))
}
COMMANDS <- as.character(body(matrix))
COMMANDS
[1] "{"
[2] "if (is.object(data) || !is.atomic(data)) data <- as.vector(data)"
[3] ".Internal(matrix(data, nrow, ncol, byrow, dimnames, missing(nrow), missing(ncol)))"
但是,我不知道如何逆向这个过程——也就是说,如果我构建了一个包含我的命令的字符向量,我该如何创建一个以这组命令为主体的函数?
可以使用 as.function
从列表创建函数。该列表必须包含一个类型为 "language"
的未命名元素,它充当函数的主体,列表中的任何命名项都被解释为函数的参数。
要从字符串创建函数体,我们可以使用 str2lang
.
将字符串转换为语言对象
这意味着我们可以很容易地编写一个函数,从这样的字符串创建函数:
make_func <- function(..., body) {
as.function(c(..., str2lang(body)))
}
所以,例如:
hello <- make_func(body = "print('hello world')")
hello()
#> [1] "hello world"
或者,如果我们的函数需要参数:
add_one <- make_func(x = 1, body = "{ x <- x + 1; return(x) }")
add_one(22)
#> [1] 23
正如其他人在评论中指出的那样,很少需要执行转换为字符的步骤,因为我们可以直接使用语言对象。
例如,要重新创建 matrix
,我们可以这样做:
my_matrix <- as.function(c(formals(matrix), body(matrix)))
my_matrix(1:9, ncol = 3)
#> [,1] [,2] [,3]
#> [1,] 1 4 7
#> [2,] 2 5 8
#> [3,] 3 6 9
如果出于某种原因,您有一个表示多行代码的字符向量,您可以简单地 paste
将它们与换行符一起使用。请注意,您需要确保结果用花括号括起来,以便 str2lang
对其进行解析。所以在你的例子中,我们可以这样做:
my_mat <- make_func(data = NA, ncol = 1, nrow = 1, byrow = FALSE,
dimnames = list(NULL),
body = paste0(paste(code_lines, collapse = '\n'), '}'))
my_mat(1:9, ncol = 3)
#> [,1] [,2] [,3]
#> [1,] 1 4 7
#> [2,] 2 5 8
#> [3,] 3 6 9
为了完整起见,我将添加另一个答案,将所需的 material 合并为一个函数 generate_function
以生成输出函数。该函数采用一组参数和一个(字符)命令向量,并生成实现这些命令的 output-function。
generate_function <- function(..., commands, envir = parent.frame(),
openbracket = TRUE, closebracket = FALSE) {
body <- paste(commands, collapse = '\n')
if (!openbracket) { body <- paste(c('{', body), collapse = '\n') }
if (!closebracket) { body <- paste(c(body, '}'), collapse = '\n') }
as.function(c(..., str2lang(body)), envir = envir) }
我们可以应用此函数来恢复问题中用作示例的matrix
函数。为此,我们列出了与函数中相同的参数,并将其命令用作输入。
COMMANDS <- as.character(body(matrix))
FUNCTION <- generate_function(data = NA, nrow = 1, ncol = 1, byrow = FALSE,
dimnames = list(NULL), commands = COMMANDS)
我们现在可以确认新创建函数的形式和主体与 matrix
函数匹配。 (函数的环境不同,因为新函数存在于我们正在工作的父环境中,正如我们所希望的那样。)
identical(body(FUNCTION), body(matrix))
[1] TRUE
identical(formals(FUNCTION), formals(matrix))
[1] TRUE
我正在尝试构建一个函数,该函数从其输入创建并输出一个新的 函数(为方便起见,我将其称为输出函数)。我能够为输出函数生成我想要的代码行作为字符串,并将其放入一个字符向量中,该向量通过函数主体中我想要的命令。但是,我无法将其转换为输出函数。
我观察到我可以使用 body
命令提取函数的主体,并将其转换为字符向量。例如,以下是将此应用于 matrix
函数时得到的结果:
body(matrix)
{
if (is.object(data) || !is.atomic(data))
data <- as.vector(data)
.Internal(matrix(data, nrow, ncol, byrow, dimnames, missing(nrow),
missing(ncol)))
}
COMMANDS <- as.character(body(matrix))
COMMANDS
[1] "{"
[2] "if (is.object(data) || !is.atomic(data)) data <- as.vector(data)"
[3] ".Internal(matrix(data, nrow, ncol, byrow, dimnames, missing(nrow), missing(ncol)))"
但是,我不知道如何逆向这个过程——也就是说,如果我构建了一个包含我的命令的字符向量,我该如何创建一个以这组命令为主体的函数?
可以使用 as.function
从列表创建函数。该列表必须包含一个类型为 "language"
的未命名元素,它充当函数的主体,列表中的任何命名项都被解释为函数的参数。
要从字符串创建函数体,我们可以使用 str2lang
.
这意味着我们可以很容易地编写一个函数,从这样的字符串创建函数:
make_func <- function(..., body) {
as.function(c(..., str2lang(body)))
}
所以,例如:
hello <- make_func(body = "print('hello world')")
hello()
#> [1] "hello world"
或者,如果我们的函数需要参数:
add_one <- make_func(x = 1, body = "{ x <- x + 1; return(x) }")
add_one(22)
#> [1] 23
正如其他人在评论中指出的那样,很少需要执行转换为字符的步骤,因为我们可以直接使用语言对象。
例如,要重新创建 matrix
,我们可以这样做:
my_matrix <- as.function(c(formals(matrix), body(matrix)))
my_matrix(1:9, ncol = 3)
#> [,1] [,2] [,3]
#> [1,] 1 4 7
#> [2,] 2 5 8
#> [3,] 3 6 9
如果出于某种原因,您有一个表示多行代码的字符向量,您可以简单地 paste
将它们与换行符一起使用。请注意,您需要确保结果用花括号括起来,以便 str2lang
对其进行解析。所以在你的例子中,我们可以这样做:
my_mat <- make_func(data = NA, ncol = 1, nrow = 1, byrow = FALSE,
dimnames = list(NULL),
body = paste0(paste(code_lines, collapse = '\n'), '}'))
my_mat(1:9, ncol = 3)
#> [,1] [,2] [,3]
#> [1,] 1 4 7
#> [2,] 2 5 8
#> [3,] 3 6 9
为了完整起见,我将添加另一个答案,将所需的 material 合并为一个函数 generate_function
以生成输出函数。该函数采用一组参数和一个(字符)命令向量,并生成实现这些命令的 output-function。
generate_function <- function(..., commands, envir = parent.frame(),
openbracket = TRUE, closebracket = FALSE) {
body <- paste(commands, collapse = '\n')
if (!openbracket) { body <- paste(c('{', body), collapse = '\n') }
if (!closebracket) { body <- paste(c(body, '}'), collapse = '\n') }
as.function(c(..., str2lang(body)), envir = envir) }
我们可以应用此函数来恢复问题中用作示例的matrix
函数。为此,我们列出了与函数中相同的参数,并将其命令用作输入。
COMMANDS <- as.character(body(matrix))
FUNCTION <- generate_function(data = NA, nrow = 1, ncol = 1, byrow = FALSE,
dimnames = list(NULL), commands = COMMANDS)
我们现在可以确认新创建函数的形式和主体与 matrix
函数匹配。 (函数的环境不同,因为新函数存在于我们正在工作的父环境中,正如我们所希望的那样。)
identical(body(FUNCTION), body(matrix))
[1] TRUE
identical(formals(FUNCTION), formals(matrix))
[1] TRUE