Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

with_tempfile #32

Closed
jimhester opened this issue Nov 3, 2015 · 6 comments
Closed

with_tempfile #32

jimhester opened this issue Nov 3, 2015 · 6 comments

Comments

@jimhester
Copy link
Member

This takes an idea from bquote() and allows you to specify what variable to assign the temporary filename to in your code by surrounding it in a .() call. Then these files are cleaned up afterwards. The function definition and example usage is below. An alternative interface would be to have a separate argument to specify variable names to use, but I think it is a little nicer to specify them inline. Any thoughts @krlmlr?

with_tempfile <- function(code, envir = parent.frame()) {
  # Adapted from bquote
  vars <- character()
  unquote <- function(e) {
    if (is.pairlist(e))
      as.pairlist(lapply(e, unquote))
    else if (length(e) <= 1) e
    else if (e[[1]] == as.name(".")) {
      vars <<- append(vars, as.character(e[[2]]))
      e[[2]]
    }
    else as.call(lapply(e, unquote))
  }
  code <- unquote(substitute(code))
  if (length(vars) == 0) {
    stop("Must specify a variable to store the temp filename using .() in your
         code", call. = FALSE)
  }

  env <- new.env(parent = envir)
  for (var in vars) {
    assign(var, tempfile(), envir = env)
  }
  on.exit(unlink(mget(vars, envir = env)))

  eval(code, envir = env)
}

files <- character()

with_tempfile({
  writeLines("hi", con = .(tmp))
  writeLines("ho", con = .(tmp2))
  print(readLines(tmp))
  print(readLines(tmp2))
  files <<- append(files, tmp)
  files <<- append(files, tmp2)
})
#> [1] "hi"
#> [1] "ho"

readLines(files[1])
#> Warning in file(con, "r"): cannot open file '/tmp/RtmpjcsoCQ/
#> filecc56011eda5': No such file or directory
#> Error in file(con, "r"): cannot open the connection
readLines(files[2])
#> Warning in file(con, "r"): cannot open file '/tmp/RtmpjcsoCQ/
#> filecc52fad8b97': No such file or directory
#> Error in file(con, "r"): cannot open the connection
@krlmlr
Copy link
Member

krlmlr commented Nov 3, 2015

Honestly, I think withr::get_current() or withr::get_current_tempfile() (as in #30) would lead to simpler code. What about nested calls?

A function withr::scope_tempfile() could simply return the name of that file; implementing a scope_() function based on later::defer() shouldn't be too difficult (#28).

@jimhester
Copy link
Member Author

Ditching the .() business, which doesn't handle nesting properly makes this very simple. No need for any additional infrastructure.

with_tempfile <- function(new, code, envir = parent.frame()) {
  env <- new.env(parent = envir)
  for (f in new) {
    assign(f, tempfile(), envir = env)
  }
  on.exit(unlink(mget(new, envir = env)))
  eval(substitute(code), envir = env)
}

with_tempfile("tmp1", {
  writeLines(con = tmp1, "hi1")
  print(readLines(tmp1))
  print(tmp1)
  with_tempfile("tmp2", {
    writeLines(con = tmp2, "hi2")
    print(readLines(tmp1))
    print(readLines(tmp2))
    print(tmp1)
    print(tmp2)
  })
})
#> [1] "hi1"
#> [1] "/tmp/RtmpjcsoCQ/filecc56e5cb51e"
#> [1] "hi1"
#> [1] "hi2"
#> [1] "/tmp/RtmpjcsoCQ/filecc56e5cb51e"
#> [1] "/tmp/RtmpjcsoCQ/filecc551757b92"

@krlmlr
Copy link
Member

krlmlr commented Nov 3, 2015

This is nice. I wonder what the factual difference between force(code) and eval(substitute(code)) is. Could there be any corner cases that aren't handled properly by eval+substitute?

Will there be any "no visible global binding for ..." warnings with that code?

@jimhester
Copy link
Member Author

I don't think there is any real difference between force(code) and eval(substitute(code), envir = parent.frame()), I believe they both evaluate the code in the calling environment.

There will be the global binding warnings, but that would be true for any of the methods no? One can always register them with utils::globalVariables() to remove the warning as well.

f <- function() {
  with_tempfile("tmp1", {
    writeLines(con = tmp1, "hi1")
    print(readLines(tmp1))
  })
}

codetools::findGlobals(f, merge = F)$variables
#> [1] "tmp1"

@krlmlr
Copy link
Member

krlmlr commented Nov 3, 2015

There will be no warning with scoping functions (#33):

scope_dir <- scope_(setwd)
old_wd <- scope_dir(new_wd)
...

@krlmlr
Copy link
Member

krlmlr commented Nov 3, 2015

I like both interfaces: with_ and scope_. I'm thinking about a way to support both, for cases such as with_tempfile().

jimhester added a commit that referenced this issue Aug 31, 2017
jimhester added a commit that referenced this issue Aug 31, 2017
jimhester added a commit that referenced this issue Aug 31, 2017
jimhester added a commit that referenced this issue Aug 31, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants