1

我想要一个粘贴逻辑表达式的功能

paste_logic(a == b, c > q, f < g, sep = and) 
# should return
# expr(a == b & c > q & f < g)

我还想在 ruturning 期间(而不是在函数调用中)懒惰地取消引用,理想情况下控制哪一边

paste_expr(paste_expr(a == b, c > q, f < g, sep = and, side = right)
# should return
# expr(a == !!b & c > !!q & f < !!g)

我实现第一个目标的解决方案是:

paste_logic <- function(sep, ...) {
  dots <- enquos(...)
  sep <- enexpr(sep)
  dispatch <- function(symbol) if (symbol == expr(and)) `&` else `|`
  if (length(dots) == 1) {
    dots[[1]]
  } else {
    expr(`!!`(dispatch(sep))(!!(dots[[1]]), !!paste_logic(sep, !!!dots[-1])))
  }
}
paste_logic(and, a > b, c == d, k == f) 
# returns
# .Primitive("&")(~a > b, .Primitive("&")(~c == d, ~k == f))

a <- 1
b <- 2
c <- 3
d <- 3
k <- 9
f <- 10

eval_tidy(paste_logic(and, a > b, c == d, k == f))
# returns FALSE
eval_tidy(paste_logic(or, a > b, c == d, k == f))
# returns TRUE

两者都符合预期。

关于如何改进这段代码并实现第二个目标(在返回的表达式中取消引用),我有几个问题。

Q1。在最后一个else {...}闭包中的这一部分:

expr(`!!`(dispatch(sep))(!!(dots[[1]]), !!paste_logic(sep, !!!dots[-1])))

我必须使用素数符号来包装!!运算符或使用UQ函数。如果我简单地将它作为!!(dispatch(sep))或完整的功能定义作为这个

paste_logic <- function(sep, ...) {
  dots <- enquos(...)
  sep <- enexpr(sep)
  dispatch <- function(symbol) if (symbol == expr(and)) `&` else `|`
  if (length(dots) == 1) {
    dots[[1]]
  } else {
    expr(!!(dispatch(sep))(!!(dots[[1]]), !!paste_logic(!!sep, !!!dots[-1])))
  }
}
paste_logic(or, a > b, c == d, k == f)

它返回错误

Error: Quosures can only be unquoted within a quasiquotation context.

  # Bad:
  list(!!myquosure)

  # Good:
  dplyr::mutate(data, !!myquosure)

在全球环境中测试

a <- 1
b <- 2
c <- `&`
expr(!!(c)(!!a, !!n))

工作正常,没有错误并返回TRUE。那么,为什么在我的代码中这不起作用而我必须使用<prime>!!<prime>

Q2。我必须使用逻辑运算符的前缀函数版本,因此最终表达式是对.Primitive("&").

有没有办法从函数外部传递&|作为符号,所以我得到最终表达式为expr(a == b & c > q & f < g)

简单地包装&|withensymenexpr内部函数体会产生如下错误:Error: unexpected '&' in "expr(&"

第三季度。此解决方案不支持在返回的表达式中进一步取消引用,例如

expr(a == !!b & c > !!q & f < !!g)

因为每个dots[[i]]都是一个单独的表达式a == b,我无法进一步分解和操作。定义不被引用的一面甚至更难完成。有什么简单的方法可以实现这一目标吗?

4

2 回答 2

2

我认为您正在寻找减少操作:

exprs_reduce <- function(xs, op) {
  n <- length(xs)

  if (n == 0) {
    return(NULL)
  }

  if (n == 1) {
    return(xs[[1]])
  }

  # Replace `call()` by `call2()` to support inlined functions
  purrr::reduce(xs, function(out, new) call(op, out, new))
}

exprs_reduce(alist(), "&")
#> NULL

exprs_reduce(alist(foo), "&")
#> foo

exprs_reduce(alist(foo, bar), "&")
#> foo & bar

exprs_reduce(alist(foo, bar, baz), "|")
#> foo | bar | baz
于 2021-10-26T10:21:46.170 回答
0

在解决您的问题的一般方法中,我会遵从 Lionel Henry 的专业知识,但是要让您的功能完全按照您的要求执行,您可以尝试以下方法:

library(rlang)

paste_logic <- function(sep, ..., side = "none")
{
  elements <- as.list(match.call())[-1]
  sep <- elements$sep
  elements <- elements[!nzchar(names(elements))]

  sep <- if(sep == expr(and)) " & " else " | "
  if(side == "right") {
    elements <- lapply(elements, function(x) {
      x <- as.character(x); 
      x[3] <- paste0("!!", x[3]); 
      str2lang(paste(x[c(2, 1, 3)], collapse = " "))})
  }
  if(side == "left") {
    elements <- lapply(elements, function(x) {
      x <- as.character(x); 
      x[2] <- paste0("!!", x[2]); 
      str2lang(paste(x[c(2, 1, 3)], collapse = " "))})
  }
  result <- do.call(function(...) paste(..., sep = sep), 
                    lapply(elements, capture.output))
  return(str2lang(result))
}

这将返回实际的语言对象,以及可选的 bang-bang 运算符:

paste_logic(and, a == b, c > q, f < g)
#> a == b & c > q & f < g

paste_logic(or, a == b, c > q, f < g)
#> a == b | c > q | f < g

paste_logic(and,  a == b, c > q, f < g, side = "left")
#> !!a == b & !!c > q & !!f < g

paste_logic(and,  a == b, c > q, f < g, side = "right")
#> a == !!b & c > !!q & f < !!g

当然,这些可以按预期进行评估:

a <- 1
b <- 2
c <- 3
d <- 3
k <- 9
f <- 10

eval_tidy(paste_logic(and, a > b, c == d, k == f))
#> [1] FALSE

eval_tidy(paste_logic(or, a > b, c == d, k == f))
#> [1] TRUE

reprex 包于 2021-10-26 创建 (v2.0.0 )

于 2021-10-26T10:45:54.503 回答