【问题标题】:Why does lazy evaluation "lose" a function argument?为什么惰性求值会“丢失”函数参数?
【发布时间】:2019-12-09 11:53:29
【问题描述】:

我正在处理 SQL 和用户输入。所以我使用glue 库来处理参数化查询。

但是,为了保持整洁,我将它们全部封装在一个函数中:

safeQuery <- function(con, sql, ...) {  
  sql = glue_sql(sql, ..., .con=con)
  query <- dbSendQuery(con, sql)
  out <- dbFetch(query)
  dbClearResult(query)
  return(out)
}

所以我只是用glue_sql 适当绑定的SQL 代码的连接、SQL 代码和参数列表调用该函数。

这很好用。

现在,我有一个特定的 SQL 调用,我经常以一种或另一种方式使用它,但参数不同。

所以我决定为此创建一个函数:

get_data <- function(con, params) {
  safeQuery(con,
            "SELECT *
             FROM foo
             WHERE bar IN ({vars*})",
            vars=params)
}
p = c(1, 2)
get_data(con, p) 

因此,用户数据(在本例中为 c(1, 2))将被传递给 get_data,后者会将其与 SQL 调用一起传递给 safeQueryglue_sql 将负责绑定。

但是,如果我真的尝试运行 get_data,我会收到一个错误

object 'params' not found

谷歌搜索和 SO'ing 已经清楚地表明这与 R 的惰性评估有关。

确实,将get_data 更改为

get_data <- function(con, params) {
  do.call("safeQuery",
          list(con,
               "SELECT *
                FROM foo
                WHERE bar IN ({vars*})",
               vars=params)
}

(由this answer 推荐)工作得很好,因为do.call 在将列表中的参数发送到safeQuery 之前评估它们。

我不明白为什么这首先是必要的。毕竟,params 的值在到达glue_sql 的过程中的任何一步都没有被修改,所以它应该仍然可用。

链接的答案讨论了使用substitute(我还阅读了有关该主题的this R-bloggers post)将参数的名称替换为调用者的名称(或者如果直接给出参数值,则使用其实际值),但这对我来说不起作用。修改get_data使用substitute

get_data <- function(con, params) {
  do.call("safeQuery",
          list(con,
               "SELECT *
                FROM foo
                WHERE bar IN ({vars*})",
               vars=substitute(params))
}

导致来自glue_sql的以下SQL:

SELECT *
FROM foo
WHERE bar IN (params)

而不是params 的实际值。我无法在safeQuery 中尝试相同的操作,因为参数隐藏在... 中并且substitute(...) 不起作用。我试过了。

我也尝试过在get_data 的开头调用force(params),但这给出了相同的object not found 错误。

get_data <- function(con, params) {
  force(params)
  do.call("safeQuery",
          list(con,
               "SELECT *
                FROM foo
                WHERE bar IN ({vars*})",
               vars=params)
}

那么,为什么params 会在标准调用中“迷失”?为什么do.call 有效,而force(params) 无效?是否可以使用标准评估来完成这项工作?

我不会撒谎:这种经历让我对如何编写函数和处理它们的参数感到困惑(从现在开始,我正在考虑只使用do.call)。如果可以在不过度扩展此问题范围的情况下提供提示,我将非常感激。

【问题讨论】:

  • 你从来没有表现出你如何称呼get_data,也许这可能有助于我理解出了什么问题。你能把它包括进去吗?
  • @r2evans 哎呀。在第一次定义后立即通过调用 get_data 的示例编辑了问题。
  • 如果safeQuery 的第一行是do.call(glue_sql, c(sql, list(...), .con=con)),它对我有用。我不确定为什么(有趣的问题),但它有效。 (我知道这只是您的段落“将 get_data 更改为...” 的不同观点。)

标签: r lazy-evaluation r-glue


【解决方案1】:

我不完全清楚为什么会这样,但确实如此。

safeQuery <- function(con, sql, ...) {
  dots  = list(...)
  dots
}

然后当你调用get_data("foo_con", params = 1:3),你会得到:

$`vars`
[1] 1 2 3

所以现在我们在一个命名列表中有参数,这意味着你应该使用glue_data(或glue_data_sql):

safeQuery <- function(con, sql, ...) {
  dots  = list(...)
  glue_data_sql(.x = dots, sql, .con=con)
  # More code...
}

现在当您拨打get_data("foo_con", params = 1:3) 时,您会得到:

<SQL> SELECT *
FROM foo
WHERE bar IN (1, 2, 3)

替代版本:

问题在于在哪个环境中评估参数。强制执行此操作的一种方法是传递您想要的环境:

safeQuery <- function(con, sql, ..., .envir = parent.frame()) {
  dots  = list(...)
  glue_sql(sql, ..., .con=con, .envir = .envir)
  # More code...
}

get_data <- function(con, params) {

  env <- environment()

  safeQuery(con,
            "SELECT *
            FROM foo
            WHERE bar IN ({vars*})",
            vars=params, .envir = env)
}

get_data("foo_con", params = 1:3)
<SQL> SELECT *
FROM foo
WHERE bar IN (1, 2, 3)

【讨论】:

  • 我现在无法访问我的计算机,因此无法自行检查。然而,看着最后一块,看起来不太对劲。 glue_sql 应该输出一个以 IN (1, 2, 3) 结尾的 SQL 调用。
  • @Wasabi 你说得对,我在摆弄get_data 函数时转换了*。它现在返回正确的输出。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-09-20
  • 2011-02-06
  • 1970-01-01
  • 2017-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多