rexpressionmethod-call

`quote()` and `call()`: How to distinguish between both outputs?


Motivation

I am programming a function that takes a call as an input. I notice however that R has two different ways to create calls, either through call() or quote(). My code behaves differently depending on which function was called, so I would like to convert both types of objects to a single one to avoid expanding the code.

Example

Consider the following two objects:

call_obj <- "rnorm(5)" |> call()
quote_obj <- rnorm(5) |> quote()

Note that they print different output in the console:

> call_obj
`mean()`()
> quote_obj
mean()

Yet, they have the same class, type and mode:

> class(call_obj)
[1] "call"
> class(quote_obj)
[1] "call"
> mode(call_obj)
[1] "call"
> mode(quote_obj)
[1] "call"
> typeof(call_obj)
[1] "language"
> typeof(quote_obj)
[1] "language"
> str(call_obj)
 language `mean()`()
> str(quote_obj)
 language mean()

Distinguish

I have been able to distinguish both objects through two approaches.

The simple one is through length(). Since call_obj seems to always return a length of 1, whereas quote_obj returns a length equal to the number of explicit arguments in the call + 1. This approach fails however if the function is one that can take no explicit arguments and runs only with defaults, such as mean(). Is there a more robust concise alternative? Tried alternatives with grepl and backticks but they do not seem to detect them even if they are visible in the str() print output.

The complicated one is leveraging the fact that call_obj can be converted into quote_obj by

> all.equal(call_obj[[1]] |> as.character() |> str2lang(), quote_obj)
[1] TRUE

whereas the same functions applied to an object that is already a quote, will return FALSE:

> all.equal(quote_obj[[1]] |> as.character() |> str2lang(), quote_obj)
[1] "Modes of target, current: name, call"
[2] "target is a subset of current"   

Bonus Question: What is a concise way to convert a quote_obj so it is equal to a call_obj?


If there's something very non-R of my approach here and there are more idiomatic approaches please comment it so!


Solution

  • Your example call_obj <- "rnorm(5)" |> call() produces a call to a function named "rnorm(5)" with no argument, not a call to "rnorm" with argument 5. If that's what you really want, then comparing as.character(call_obj[[1]])) to as.character(quote_obj[[1]]) will show the difference.

    If you really want both to be calls to rnorm(5), then you need to use

    call_obj <- "rnorm" |> call(5)
    

    and the result will be identical to what you get in quote_obj.