Search code examples
rdplyrnon-standard-evaluationquosure

Converting a quoted list into a list of quoted expressions for dplyr::filter


I am writing a function that combines a dplyr::filter step (which I want to parametrize) before doing some other stuff. I want provide a default argument for the filtering criteria that can be overridden. This precludes passing the filtering arguments in with ... One attempt, would be the following:

library(rlang)
library(dplyr)
filter_and_stuff1 = function(tbl, filter_args = list(mpg > 33, gear == 4), arg3, arg4){
    as_expr = enexpr(filter_args)
    sub_tbl = filter(tbl, !!!as_expr)
    # do some other things with sub_tbl, arg3 and arg4
    sub_tbl
}

But

filter_and_stuff1(mtcars)
Error: Argument 2 filter condition does not evaluate to a logical vector

It seems that the comma separation creates a problem. Looking inside dplyr's code, it's handled with a call to the internal function quo_reduce, which seems to splice together the comma separated values with &. I don't understand how to do this without use of ...

TLDR: How can I programmatically pass a set of arguments to dplyr::filter that includes a default expression?


Solution

  • The problem is that, with the way you're using it, enexpr also captures the call to list, whereas using the ellipsis already separates each expression into different elements of a list:

    library(rlang)
    
    foo <- function(x) {
        enexpr(x)
    }
    
    foo(list(a, b, c))
    # list(a, b, c)
    
    bar <- function(...) {
        enexprs(...)
    }
    
    bar(a, b, c)
    # [[1]]
    # a
    # 
    # [[2]]
    # b
    # 
    # [[3]]
    # c
    

    To do what you want, you can use call_args to extract each expression from what was given to list:

    baz <- function(x) {
        as_expr <- enexpr(x)
        # expr just to show
        expr(filter(!!!call_args(as_expr)))
    }
    
    baz(list(a == 1, b < 2))
    # filter(a == 1, b < 2)