deparse_call

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)

TODO: reorganize, put failing alternatives on top, then really wrong stuff, then inaccuracies then readability.

constructive::deparse_call() converts calls (language objects) to code. It is an alternative to base::deparse() or rlang::expr_deparse() with a slightly different scope, and 3 main differences:

x <- call('+', c(1, 2))
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x)

# this is different
y <- quote(+c(1, 2))
x[[2]]
y[[2]]
x <- quote(`*`(a + b, c))
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x)

y <- quote((a + b) * c)
base::deparse(y)
rlang::expr_deparse(y)
constructive::deparse_call(y)

# x and y are different, parentheses are code!
x[[2]]
y[[2]]
x <- call("[")
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x)
library(constructive)
#deparse_call <- function(x) gsub("`", "\\\\`", constructive::deparse_call(x))

deparse_call <- function(x) paste(sprintf("`` %s ``", constructive::deparse_call(x)), collapse = "<br>")
deparse <- function(x) paste(sprintf("`` %s ``", base::deparse(x)), collapse = "<br>")
expr_deparse <- function(x) paste(sprintf("`` %s ``", rlang::expr_deparse(x)), collapse = "<br>")

# deparse <- function(x) as_constructive_code(base::deparse(x))
# expr_deparse <- function(x) as_constructive_code(rlang::expr_deparse(x))
compare_deparse_call <- function(x) 
  identical(unclass(constructive::deparse_call(x)), base::deparse(x)) &&
  identical(base::deparse(x), rlang::expr_deparse(x))

deparse_call() is more accurate

We present more differences below, where at least one of the alternatives is not deparsing faithfully.

| | deparse_call() | deparse() | expr_deparse() | |-------------------|------------------------------|-----------------|------------------------| | call('+', c(1, 2)) cannot be obtained by parsing code | ERROR | r deparse(call('+', c(1, 2))) | r expr_deparse(call('+', c(1, 2))) | | Infix :: and ::: can only be called on symbols | r deparse_call(call("::", 1, 2)) | r deparse(call("::", 1, 2)) | r expr_deparse(call("::", 1, 2)) | | Infix $ and @ can only have a symbol rhs | r deparse_call(call("$", "a", 1)) | r deparse(call("$", "a", 1)) | r expr_deparse(call("$", "a", 1)) | | Infix $ and @ create different calls when rhs is symbol or string | r deparse_call(call("$", quote(a), "b")) | r deparse(call("$", quote(a), "b")) | r expr_deparse(call("$", quote(a), "b")) | | Binary ops cannot be used as prefixes | r deparse_call(call("*", 1)) | r deparse(call("*", 1)) | r expr_deparse(call("*", 1)) | | Binary ops cannot be used infix with > 2 args | r deparse_call(call("*", 1, 2, 3)) | r deparse(call("*", 1, 2, 3)) | r expr_deparse(call("*", 1, 2, 3)) | | Binary ops cannot be used infix with empty args | r deparse_call(call("*", 1, quote(expr=))) | r deparse(call("*", 1, quote(expr=))) | r expr_deparse(call("*", 1, quote(expr=))) | | Parentheses need function call notation if 0 arg | r deparse_call(call("(")) | r deparse(call("(")) | r expr_deparse(call("(")) | | Parentheses need function call notation if > 1 arg | r deparse_call(call("(", 1, 2)) | r deparse(call("(", 1, 2)) | r expr_deparse(call("(", 1, 2)) | | Calling = is different from passing an arg | r deparse_call(substitute(list(X), list(X = call("=", quote(x), 1)))) | r deparse(substitute(list(X), list(X = call("=", quote(x), 1)))) | r expr_deparse(substitute(list(X), list(X = call("=", quote(x), 1)))) | | Precedence must be respected, but adding extra parentheses to respect precedence is not accurate | r deparse_call(str2lang("'-'(1+2)")) | r deparse(str2lang("'-'(1+2)")) | r expr_deparse(str2lang("'-'(1+2)")) | | | r deparse_call(quote('+'(repeat { }, 1))) | r deparse(quote('+'(repeat { }, 1))) | r expr_deparse(quote('+'(repeat { }, 1))) | | | r deparse_call(quote(1 -> x <- 2)) | r deparse(quote(1 -> x <- 2)) | r expr_deparse(quote(1 -> x <- 2)) | | | r deparse_call(str2lang("'*'('+'(a, b), c)")) | r deparse(str2lang("'*'('+'(a, b), c)")) | r expr_deparse(str2lang("'*'('+'(a, b), c)")) | | | r deparse_call(str2lang("'+'(x, y)(z)")) | r deparse(str2lang("'+'(x, y)(z)")) | r expr_deparse(str2lang("'+'(x, y)(z)")) | | | r deparse_call(str2lang("'^'('^'(1, 2), 4)")) | r deparse(str2lang("'^'('^'(1, 2), 4)")) | r expr_deparse(str2lang("'^'('^'(1, 2), 4)")) | | | r deparse_call(str2lang("'+'(1, '+'(2, 3))")) | r deparse(str2lang("'+'(1, '+'(2, 3))")) | r expr_deparse(str2lang("'+'(1, '+'(2, 3))")) | | Brackets calling no arg is different from subsetting NULL | r deparse_call(call("[")) | r deparse(call("[")) | r expr_deparse(call("[")) | | Empty bracket syntax means doesn't mean no 2nd arg, it means 2nd arg is empty symbol, so for 1 arg we need function notation | r deparse_call(call("[", quote(x))) | r deparse(call("[", quote(x))) | r expr_deparse(call("[", quote(x))) | | Brackets with an empty first arg need function call notation | r deparse_call(call("[", quote(expr=), quote(expr=))) | r deparse(call("[", quote(expr=), quote(expr=))) | r expr_deparse(call("[", quote(expr=), quote(expr=))) | | Brackets taking a call to a lower precedence op as a first arg need function call notation | r deparse_call(call("[", quote(a+b), 1)) | r deparse(call("[", quote(a+b), 1)) | r expr_deparse(call("[", quote(a+b), 1)) | | Invalid function definitions can be valid code | r deparse_call(call("function", 1,2)) | ERROR | SEGFAULT | | | r deparse_call(quote('function'(1(2), 3))) | r deparse(quote('function'(1(2), 3))) | ERROR | | Curly braces need function call notation if they have empty args | r deparse_call(call("{", 1, quote(expr = ))) | r deparse(call("{", 1, quote(expr = ))) | r expr_deparse(call("{", 1, quote(expr = ))) | | Control flow constructs need function call notation if they're used as callers | r deparse_call(quote('if'(TRUE, { })(1))) | r deparse(quote('if'(TRUE, { })(1))) | r expr_deparse(quote('if'(TRUE, { })(1))) | | Symbols with non syntactic names need backquotes | r deparse_call(as.symbol("*a*")) | r deparse(as.symbol("*a*")) | r expr_deparse(as.symbol("*a*")) | | This includes emojis | r deparse_call(as.symbol("🐶")) | r deparse(as.symbol("🐶")) | r expr_deparse(as.symbol("🐶")) |

deparse_call() is clearer

In the following base::deparse() and rlang::expr_deparse() are not wrong, but constructive::deparse_call() is clearer.

| | constructive::deparse_call() | base::deparse() | rlang::expr_deparse() | |------------------------|------------------------|------------------------|------------------------| | Simple quotes make strings that use double quotes more readable | r deparse_call('"oh" "hey" "there"') | r deparse('"oh" "hey" "there"') | r expr_deparse('"oh" "hey" "there"') | | Raw strings make more complex strings more readable | r deparse_call('"oh"\\\'hey\'\\"there"') | r deparse('"oh"\\\'hey\'\\"there"') | r expr_deparse('"oh"\\\'hey\'\\"there"') | | Homoglyphs are dangerous, we can use the \U{XX} notation | r deparse_call("\U{410} \U{A0} A") | r deparse("\U{410} \U{A0} A") | r expr_deparse("\U{410} \U{A0} A") | | For symbols we need the \xXX notation| r deparse_call(call("c", as.symbol("\U{410}"), "\U{A0}" = 1)) | r deparse(call("c", as.symbol("\U{410}"), "\U{A0}" = 1)) | r expr_deparse(call("c", as.symbol("\U{410}"), "\U{A0}" = 1)) | | Emojis depend on font so are ambiguous | r deparse_call("🐶") | r deparse("🐶") | r expr_deparse("🐶") |

deparse_call() fails rather than making things up

x <- call("(", -1)
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x)

# this is different! `-` is code!
y <- quote((-1))
base::deparse(y)
rlang::expr_deparse(y)
constructive::deparse_call(y)


x <- call("fun", quote(expr = ))
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x) # this is wrong!

# no agument and 1 missing argument is not the same!
y <- call("fun")
base::deparse(y)
rlang::expr_deparse(y)
constructive::deparse_call(y)

x <- call("!", quote(expr = ))
base::deparse(x)
rlang::expr_deparse(x)
constructive::deparse_call(x)


Try the constructive package in your browser

Any scripts or data that you put into this service are public.

constructive documentation built on Nov. 5, 2025, 7:14 p.m.