在将文件写入磁盘的函数中我应该 return 什么?

What I should return in a function for writing files to disk?

我正在制作一个简单的 R 函数,以相同的方式在磁盘上一致地写入文件,但在不同的文件夹中:

library(magrittr)

main_path <- here::here()

write_to_disk <- function(data, folder, name){
     data %>%
     vroom::vroom_write(
          file.path(main_path, folder, paste0(name, ".tsv"))
     )
}

我知道我不一定 需要 到 return R 函数中的任何东西,但如果我需要,什么是合适的 return()在这里声明?

非常感谢

老实说,这很容易产生意见和背景,但有一些想法:

  1. Return 原始数据。 大多数 tidyverse 动词函数和许多其他包(以及一些在基础右)。如果您使用 %>%|> 管道,这样做可以在您的函数之后处理数据,这可能非常方便。

    write_to_disk <- function(data, folder, name){
      vroom::vroom_write(
        data,
        file.path(folder, paste0(name, ".tsv"))
      )
      data
    }
    

    (您的函数已经隐含地执行了此操作,因为对 vroom::vroom_write 的调用是函数主体中的最后一个表达式。)

  2. Return 文件写入调用的输出。 坦率地说,我不太喜欢这个,因为如果你改变您的包装器正在使用哪个函数,那么您函数的 return 值很可能会发生变化。我不知道你的包装函数的预期生命周期,但想象一下,如果你选择从 vroom::vroom 切换到另一个函数; vroom return 是基于 col_select 的数据子集,也许较新的函数将 return 整个数据,这可能会破坏下游处理的假设。

    write_to_disk <- function(data, folder, name){
      out <- vroom::vroom_write(
        data,
        file.path(folder, paste0(name, ".tsv"))
      )
      out
    }
    

    注意:我明确选择将其捕获到 out 和 return 中,以防您在 vroom::vroom_write 和随后的 out 之间添加代码。未更改的原始函数实际上在做同样的事情,但是如果您选择做任何事情 post-vroom_write,那么这个额外的步骤将是必要的。

    否则,您的函数已经在隐式执行此操作,因为 vroom::vroom_write return 是数据。

  3. Return 文件名。 这仅在不一定事先知道文件名时有用。例如,如果您的包装器小心翼翼地不覆盖同名文件,它可能会添加一个计数器(预扩展)以便永远不会发生覆盖。在这种情况下,调用环境 不知道 选择的文件名是什么,因此它具有价值(有时)。

    write_to_disk <- function(data, folder, name){
      # file numbering
      re <- paste0("^", name, "_?([0-9]+)?\.tsv$")
      existfiles <- list.files(folder, pattern = re, full.names = TRUE)
      nextnum <- max(0L, suppressWarnings(as.integer(gsub(re, "\1", basename(existfiles)))), na.rm = TRUE)
      if (nextnum > 0) {
        name <- sprintf("%s_%03i", name, nextnum + 1L)
      }
      filename <- file.path(folder, paste0(name, ".tsv"))
      vroom::vroom_write(
        data,
        filename
      )
      filename
    }
    

    (提供的“文件编号”代码仅作为我认为 returning 文件名可能有意义的示例。)

  4. Return写函数成功这可能需要使用trytryCatch (或任何 tidyverse 等价物),捕捉错误,并做出相应的反应。

    write_to_disk <- function(data, folder, name){
      res <- tryCatch(
        vroom::vroom_write(
          data,
          file.path(folder, paste0(name, ".tsv"))
        ),
        error = function(e) e
      )
      out <- !inherits(res, "error")
      if (!out) {
        attr(out, "error") <- conditionMessage(res)
      }
      out
    }
    
  5. Return nothing. 这当然是最简单的。您需要明确地执行此操作,以免无意中 return 来自文件写入函数的 return 值。

    write_to_disk <- function(data, folder, name){
      vroom::vroom_write(
        data,
        file.path(folder, paste0(name, ".tsv"))
      )
      NULL
    }
    

备注:

  1. 您对 main_path 的使用是 counter 函数式编程,因为函数在给定相同输入的情况下会根据外部事物的存在而表现不同它的直接范围。我认为传递 write_to_dist(x, file.path(main_path, folder), "somename") 更好,因为 main_path 是在 that 环境(不在函数内)中定义的,并且您的函数足够通用,不需要正确定义变量。

    我更新了上面的所有代码以反映这一良好做法。如果您强烈反对这一点,请随时在您喜欢的位置添加 main_path

  2. returned invisible 可能对以上任何一项都有用,这样(例如)可以节省大量 data.frame 不捕获其 return 值不会使控制台充满数据。这很容易用 invisible(data) 来做,并且不会改变 return 值(除了默认情况下它不打印在控制台上)。

  3. 仅供参考:Konrad 和我在关于 return(.) 是否是个好主意的评论中来回讨论。我并不反对大多数说法,并且认为它与风格和观点一样重要,而不是其他方面。无论如何,由于我的大部分论点 for return 在上述所有代码中都没有实际意义,为了简洁起见,我删除了它。