将函数作为输入并在调用时使其表达式可见的函数

Function which takes function as input and makes its expressions visible when called

this SO question 的基础上,我想编写一个函数,通过 (1) 将每一行设置为可见 () 和 (2) 将 withAutoprint({}) 包裹在正文周围来操纵其他函数的功能。首先,我虽然对 trace() 的一些调用会产生我想要的结果,但不知何故我无法弄清楚。

这是一个简单的例子:

# Input function foo
foo <- function(x)
{
  line1 <- x
  line2 <- 0
  line3 <- line1 + line2
  return(line3)
}

# some function which alters foo (here called make_visible() )
foo2 <- make_visible(foo)

# so that foo2 looks like this after being altered
foo2 <- function(x)
{
 withAutoprint({
  (line1 <- x)
  (line2 <- 0)
  (line3 <- line1 + line2)

  (return(line3))
 })
}

# example of calling foo2 and desired output/result
> foo2(2)
> (line1 <- x)
[1] 2
> (line2 <- 0)
[1] 0
> (line3 <- line1 + line2)
[1] 2
> (return(line3))
[1] 2

背景/动机

当没有抛出真正的错误时,逐行显示函数对于更长的自定义函数很有帮助,但是函数转错了 returns 和不需要的输出。另一种方法是使用调试器单击下一步并逐步检查每个变量。像 make_visible 这样的函数可能会在这里节省一些时间。

用例

我看到了这种函数的一个实际用例,在调试 maplapply 函数时,这些函数没有通过错误,但在函数的某处产生了不希望的结果循环结束。

我想出了两种不同的方法来解决我自己的上述问题。他们都使用了我称之为 'deep function hacking' 的东西,这可能不是推荐的做法——至少看起来根本不应该这样做。在玩之前我什至不知道这是可能的。可能有更清洁和更推荐的方法来执行此操作,因此我将这个问题留给其他方法。

第一种方法

我调用第一种方法的函数make_visible。基本上,此函数使用 foo 的 body 部分构造一个新函数,并将这些部分用 for 循环包装在 ( 中,然后在 withAutoprint 中。它非常 hacky,并且只适用于函数的第一级(它不会显示更深层次的结构,例如,使用管道的函数)。

make_visible <- function(.fx) {

  if (typeof(.fx) %in% c("special", "builtin")) {
    stop("`make_visible` cannot be applied to primitive functions")
  }

  if (! typeof(.fx) %in% "closure") {
    stop("`make_visible` only takes functions of type closures as argument")
  }

  # make environment of .fx parent environment of new function environment
  org_e <- environment()
  fct_e <- environment(.fx)
  parent.env(org_e) <- fct_e

  # get formals and body of input function .f
  fct_formals <- formals(.fx)
  fct_body <- body(.fx)[-1]

  # create a minimal example function for `(`
  .f1 <- function(x) {
    (x) 
  }

  # extract its body
  .f1_body <- body(.f1)[-1]

  # build a new function .f2 by combining .f and .f1
  .f2 <- function() {}

  for (i in seq_along(1:length(fct_body))) {

    .f1_body[[1]][[2]]<- fct_body[[i]]

    body(.f2)[[1+i]] <- .f1_body[[1]]

  }

  # extract the body of new function .f2
  .f2_body <- body(.f2)[-1]

  # create a minimal example function .f3 for `withAutoprint`
  .f3 <- function() {

    withAutoprint({
      x
    })

  }

  # insert body part of .f2 into .f3
  for (j in seq_along(1:length(.f2_body))) {

    body(.f3)[[2]][[2]][[1+j]] <- .f2_body[[j]]

  }

  # give .f3 the formals of input function
  formals(.f3) <- fct_formals

  # return .f3 as new function
  .f3

}

产生以下结果:

foo2 <- make_visible(foo)
foo2(1)
> (line1 <- x)
> [1] 1
> (line2 <- 0)
> [1] 0
> (line3 <- line1 + line2)
> [1] 1
> (return(line3))
> [1] 1

这种方法有两个缺点: 1. 将每行的输出包裹在括号中降低了可读性 2. 此外,这种方法 returns 不是原始函数的值,而是具有两个元素的列表,原始结果 value 和逻辑向量 visible,这使得它更难使用此函数的输出,尤其是在 map 调用中使用它时。

foo2(1) %>% str
# > (line1 <- x)
# [1] 1
# > (line2 <- 0)
# [1] 0
# > (line3 <- line1 + line2)
# [1] 1
# > (return(line3))
# [1] 1
# List of 2
# $ value  : num 1
# $ visible: logi TRUE

purrr::map(1:3, foo2)
# > (line1 <- x)
# [1] 1
# > (line2 <- 0)
# [1] 0
# > (line3 <- line1 + line2)
# [1] 1
# > (return(line3))
# [1] 1
# > (line1 <- x)
# [1] 2
# > (line2 <- 0)
# [1] 0
# > (line3 <- line1 + line2)
# [1] 2
# > (return(line3))
# [1] 2
# > (line1 <- x)
# [1] 3
# > (line2 <- 0)
# [1] 0
# > (line3 <- line1 + line2)
# [1] 3
# > (return(line3))
# [1] 3
# [[1]]
# [[1]]$value
# [1] 1
#
# [[1]]$visible
# [1] TRUE
#
#
# [[2]]
# [[2]]$value
# [1] 2
# 
# [[2]]$visible
# [1] TRUE
# 
#
# [[3]]
# [[3]]$value
# [1] 3
# 
# [[3]]$visible
# [1] TRUE

第二种方法

虽然 make_visible 是我通过使每一行可见并将其包装在 withAutoprint 中来重写函数的想法的直接方法,但第二种方法重新考虑了这个问题。它与 'deep function hack' 相似,循环遍历原始函数的 body 部分,但这次 (1) 将它们打印到控制台,(2) 捕获它们的评估输出,(3) 将此输出打印到控制台,然后 (4) 实际评估每个 body 部分。最后调用原函数并无形中返回

reveal <- function(.fx) {

  if (typeof(.fx) %in% c("special", "builtin")) {
    stop("`reveal` cannot be applied to primitive functions")
  }

  if (! typeof(.fx) %in% "closure") {
    stop("`reveal` only takes functions of type closures as argument")
  }


  # environment handling
  # get environment of .fx and make it parent.env of reveal
  org_e <- environment()
  fct_e <- environment(.fx)
  parent.env(org_e) <- fct_e

  # get formals of .fx
  fct_formals <- formals(.fx)

  # get body of .fx without first part { 
  fct_body <- body(.fx)[-1]

  # define new function to return
  .f2 <- function() {

    # loop over the body parts of .fx
    for (.i in seq_along(1:length(fct_body))) {

      # print each body part 
      cat(paste0(as.list(fct_body)[.i],"\n"))

      # check whether eval returns output and if not use eval_tidy
      if (length(capture.output(eval(fct_body[[.i]]))) == 0) {

        # write output of eval as string
        out <- capture.output(rlang::eval_tidy(fct_body[[.i]]))

      } else {

        # write output of eval as string
        out <- capture.output(eval(fct_body[[.i]]))
      }

      # print output of evaluation
      cat(out, sep = "\n")

      # evaluate
      eval(fct_body[[.i]])

    }

    # get arguments
    .args <- match.call(expand.dots = FALSE)[-1]

    # run .fx with .args and return result invisibly 
    invisible(do.call(.fx, as.list(.args)))

  }

  # replace formals of .f2 with formals of .fx  
  formals(.f2) <- fct_formals

  # replace environment of .f2 with env of reveal to which env of .fx is a parent environment
  environment(.f2) <- org_e

  # return new function .f2
  .f2

}

输出看起来相似但更清晰:

reveal(foo)(1)
> line1 <- x
> [1] 1
> line2 <- 0
> [1] 0
> line3 <- line1 + line2
> [1] 1
> return(line3)
> [1] 1

第二种方法更好,因为它更易读,并且 returns 与原始函数的值相同。但是,目前我无法让它在 map 调用中工作。这可能是由于扰乱了函数环境。

foo2 <- reveal(foo)
purrr::map(1:3, foo2)
#>  Error in (function (x)  : object '.x' not found 

这是一个解决方案,它完全创建了您在问题中提出的解决方案的主体,并添加了您在答案中使用的 2 个测试:

make_visible <- function(f) {
  if (typeof(f) %in% c("special", "builtin")) {
    stop("make_visible cannot be applied to primitive functions")
  }

  if (! typeof(f) %in% "closure") {
    stop("make_visible only takes functions of type closures as argument")
  }
  f2 <- f
  bod <- body(f)
  if(!is.call(bod) || !identical(bod[[1]], quote(`{`)))
    bod <- call("(",body(f))
  else
    bod[-1] <- lapply(as.list(bod[-1]), function(expr) call("(", expr))
  body(f2) <- call("[[",call("withAutoprint", bod),"value")
  f2
}
# solve foo issue with standard adverb way
foo <- function(x)
{
  line1 <- x
  line2 <- 0
  line3 <- line1 + line2
  return(line3)
}

foo2 <- make_visible(foo)

foo2
#> function (x) 
#> withAutoprint({
#>     (line1 <- x)
#>     (line2 <- 0)
#>     (line3 <- line1 + line2)
#>     (return(line3))
#> })[["value"]]

foo2(2)
#> > (line1 <- x)
#> [1] 2
#> > (line2 <- 0)
#> [1] 0
#> > (line3 <- line1 + line2)
#> [1] 2
#> > (return(line3))
#> [1] 2
#> [1] 2

这是另一幅作品,打印得更好,作为您自己的第二个提案:

make_visible2 <- function(f) {
  if (typeof(f) %in% c("special", "builtin")) {
    stop("make_visible cannot be applied to primitive functions")
  }

  if (! typeof(f) %in% "closure") {
    stop("make_visible only takes functions of type closures as argument")
  }
  f2 <- f
  bod <- body(f)
  if(!is.call(bod) || !identical(bod[[1]], quote(`{`))) {
    bod <- bquote({
      message(deparse(quote(.(bod))))
      print(.(bod))
    })
  }  else {
    bod[-1] <- lapply(as.list(bod[-1]), function(expr) {
      bquote({
        message(deparse(quote(.(expr))))
        print(.(expr))
      })
    })
  }
  body(f2) <- bod
  f2
}
foo3 <- make_visible2(foo)
foo3
#> function (x) 
#> {
#>     {
#>         message(deparse(quote(line1 <- x)))
#>         print(line1 <- x)
#>     }
#>     {
#>         message(deparse(quote(line2 <- 0)))
#>         print(line2 <- 0)
#>     }
#>     {
#>         message(deparse(quote(line3 <- line1 + line2)))
#>         print(line3 <- line1 + line2)
#>     }
#>     {
#>         message(deparse(quote(return(line3))))
#>         print(return(line3))
#>     }
#> }
foo3(2)
#> line1 <- x
#> [1] 2
#> line2 <- 0
#> [1] 0
#> line3 <- line1 + line2
#> [1] 2
#> return(line3)
#> [1] 2