【问题标题】:Is it possible to have `dput` return source code that would run outside of the enclosing environment?是否可以让 `dput` 返回在封闭环境之外运行的源代码?
【发布时间】:2022-01-25 06:32:19
【问题描述】:

假设我有一个闭包add_y(y),它返回一个将y 添加到其输入的函数。

add_y <- function(y) {
  function(x) {
    x + y
  }
}
add_4 <- add_y(4)

所以add_4 的值是一个将 4 添加到其输入的函数。这行得通。我想使用dput 来显示add_4 的定义为

function(x) {
  x + 4
}

但这不是 dput 返回的内容。

add_y <- function(y) {
  function(x) {
    x + y
  }
}
add_4 <- add_y(4)
dput(add_4)
#> function (x) 
#> {
#>     x + y
#> }

有没有办法获得可以在封闭环境之外运行的源代码?

【问题讨论】:

  • 为什么不在local 环境中捕获formalsargs
  • 闭包总是包含一个环境。 dput 为您提供功能而不是环境。 y 存储在环境中。如果你想要好的建议,你应该解释你为什么需要这个。为什么dput而不是序列化闭包?

标签: r closures r-environment


【解决方案1】:

如果您控制 add_y,则解决方法是将 y 的值直接注入内部函数的主体,或者将其注入形式参数列表。这消除了环境的使用,因此问题不再存在。这涉及命名匿名内部函数,并且只需要多一行来执行注入,外加一行来返回结果。

# 1. inject into body
add_y2 <- function(y) {
  inner <- function(x) {
    x + y
  }
  body(inner) <- do.call("substitute", list(body(inner)))
  inner
}
# test
add_4 <- add_y2(4)
dput(add_4)
## function (x) 
## {
##     x + 4
## }

# 2. inject into formal arguments
add_y3 <- function(y) {
  inner <- function(x) {
    x + y
  }
  formals(inner)$y <- y
  inner
}
# test
add_4 <- add_y3(4)
dput(add_4)
## function (x, y = 4) 
## {
##     x + y
## }

【讨论】:

  • 我认为您不需要更改 add_ybody(add_4) &lt;- do.call(substitute, list(body(add_4), environment(add_4))) 不应该工作吗?
  • 如果您不控制该功能,那是个好主意。
  • 这很好用,但我对为什么有点模糊!我想我需要更好地理解“替代品”的作用......关于我可以在哪里阅读的任何建议?
  • help(substitute) 将是主要的权威来源。 substitute 将在传递给它的表达式中找到的每个变量替换为指定为第二个参数的环境中的变量的值,如果未指定,则使用当前环境。
  • 就作者而言,我发现此评论比我在help(substitute) 中发现的更易读和易于理解!谢谢你:)
【解决方案2】:

这可行,但需要更改add_y 的内容。

library(rlang)
library(magrittr)
library(stringr)

add_y <- function(y) {
  fn <- expr(function(x) {
    x+!!y
  })
  fn <- deparse(fn) %>% str_c(collapse = "")
  fn <- eval(parse(text = fn))
}

add_4 <- add_y(4)

dput(add_4)
#> function (x) 
#> {
#>     x + 4
#> }

reprex package (v2.0.1) 于 2021 年 12 月 24 日创建

【讨论】:

    【解决方案3】:

    您可以构造一个 dput 替换,该替换生成的代码可以创建像 add_4 这样的函数,但它不会按照您想要的方式进行解析:

    dput_with_env <- function(f) {
      fn <- deparse(f, control = c("keepNA", "keepInteger", 
                                   "niceNames", "showAttributes"))
      env <- as.list(environment(f))
      cat("local({ f =\n")
      cat(fn, sep = "\n")
      cat("\nenvironment(f) <- list2env(\n")
      dput(env)
      cat(")\nf})")
    }
    
    add_y <- function(y) {
      function(x) {
        x + y
      }
    }
    add_4 <- add_y(4)
    
    dput_with_env(add_4)
    #> local({ f =
    #> function (x) 
    #> {
    #>     x + y
    #> }
    #> 
    #> environment(f) <- list2env(
    #> list(y = 4)
    #> )
    #> f})
    

    reprex package (v2.0.1) 于 2021 年 12 月 24 日创建

    这假设add_4 的环境非常简单,因此其环境的父级可以是您评估代码时所在的环境。我们可以试试看:

    newfn <- local({ f =
    function (x) 
    {
       x + y
    }
    environment(f) <- list2env(
    list(y = 4)
    )
    f})
    
    newfn
    #> function (x) 
    #> {
    #>    x + y
    #> }
    #> <environment: 0x7f9a1b5e2318>
    newfn(1)
    #> [1] 5
    

    reprex package (v2.0.1) 于 2021-12-24 创建

    【讨论】:

      【解决方案4】:

      不是dput(),不是。 dput() 函数不会创建环境的文本表示。

      如果你想保存函数,你可以这样做

      save(add_4, file="add4.Rdata")
      

      然后在另一个 R 会话中

      load("add4.Rdata")
      

      这将捕获所有包含的值,并且您的函数将像以前一样运行

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-07
        • 2013-12-18
        • 2012-01-26
        • 1970-01-01
        • 1970-01-01
        • 2019-11-10
        相关资源
        最近更新 更多