Skip to contents

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:

  • deparse_call() faisl if the call is not syntactic (if it cannot be the output of parse(text=x)[[1]]), for instance if its AST contains elements that are not syntactic tokens
x <- call('+', c(1, 2))
base::deparse(x)
#> [1] "+c(1, 2)"
rlang::expr_deparse(x)
#> [1] "+<dbl: 1, 2>"
constructive::deparse_call(x)
#> Error in `constructive::deparse_call()`:
#> ! `call` must only be made of symbols and syntactic literals
#> Caused by error in `deparse_call_impl()` at constructive/R/deparse_call.R:105:5:
#> ! Found element of type 'double' and length '2':
#> c(1, 2)

# this is different
y <- quote(+c(1, 2))
x[[2]]
#> [1] 1 2
y[[2]]
#> c(1, 2)
  • deparse_call() never makes compromises to make code more readable at the expense of accuracy.
x <- quote(`*`(a + b, c))
base::deparse(x)
#> [1] "(a + b) * c"
rlang::expr_deparse(x)
#> [1] "(a + b) * c"
constructive::deparse_call(x)
#> `*`(a + b, c)

y <- quote((a + b) * c)
base::deparse(y)
#> [1] "(a + b) * c"
rlang::expr_deparse(y)
#> [1] "(a + b) * c"
constructive::deparse_call(y)
#> (a + b) * c

# x and y are different, parentheses are code!
x[[2]]
#> a + b
y[[2]]
#> (a + b)
  • deparse_call() handles many more contrived cases. It strives to provide an accurate syntactic representation for every possible syntactic language object, however unprobable or unpractical they might be.
x <- call("[")
base::deparse(x)
#> [1] "NULL[]"
rlang::expr_deparse(x)
#> [1] "NULL[]"
constructive::deparse_call(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 +c(1, 2) +<dbl: 1, 2>
Infix :: and ::: can only be called on symbols `::`(1, 2) 1::2 1::2
Infix $ and @ can only have a symbol rhs `$`("a", 1) `$`("a", 1) "a"$1
Infix $ and @ create different calls when rhs is symbol or string a$"b" a$b a$"b"
Binary ops cannot be used as prefixes `*`(1) *1 `*`(1)
Binary ops cannot be used infix with > 2 args `*`(1, 2, 3) `*`(1, 2, 3) 1 * 2
Binary ops cannot be used infix with empty args `*`(1, ) 1 * 1 *
Parentheses need function call notation if 0 arg `(`() (NULL) (NULL)
Parentheses need function call notation if > 1 arg `(`(1, 2) (1) (1)
Calling = is different from passing an arg list(`=`(x, 1)) list((x = 1)) list(x = 1)
Precedence must be respected, but adding extra parentheses to respect precedence is not accurate `-`(1 + 2) -(1 + 2) -1 + 2
`+`(repeat { }, 1) (repeat {
}) + 1
repeat { } + 1
`<-`(x <- 1, 2) (x <- 1) <- 2 (x <- 1) <- 2
`*`(a + b, c) (a + b) * c (a + b) * c
`+`(x, y)(z) (x + y)(z) `+`(x, y)(z)
`^`(1^2, 4) (1^2)^4 (1^2)^4
`+`(1, 2 + 3) 1 + (2 + 3) 1 + (2 + 3)
Brackets calling no arg is different from subsetting NULL `[`() NULL[] NULL[]
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 `[`(x) x[] x[]
Brackets with an empty first arg need function call notation `[`(, ) [] []
Brackets taking a call to a lower precedence op as a first arg need function call notation `[`(a + b, 1) (a + b)[1] a + b[1]
Invalid function definitions can be valid code `function`(1, 2) ERROR SEGFAULT
`function`(1(2), 3) function(1, 2) 3 ERROR
Curly braces need function call notation if they have empty args `{`(1, ) {
1

}
{
1

}
Control flow constructs need function call notation if they’re used as callers `if`(TRUE, { })(1) (if (TRUE) {
})(1)
`if`(TRUE, { })(1)
Symbols with non syntactic names need backquotes `*a*` *a* `*a*`
This includes emojis `\xf0\x9f\x90\xb6` 🐶 `🐶`

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 '"oh" "hey" "there"' "\"oh\" \"hey\" \"there\"" "\"oh\" \"hey\" \"there\""
Raw strings make more complex strings more readable r"["oh"\'hey'\"there"]" "\"oh\"\\'hey'\\\"there\"" "\"oh\"\\'hey'\\\"there\""
Homoglyphs are dangerous, we can use the \U{XX} notation "\U{410} \U{A0} A" "А   A" "А   A"
For symbols we need the \xXX notation c(`\xd0\x90`, "\U{A0}" = 1) c(А, ` ` = 1) c(А, ` ` = 1)
Emojis depend on font so are ambiguous "\U{1F436}" "🐶" "🐶"

deparse_call() fails rather than making things up

x <- call("(", -1)
base::deparse(x)
#> [1] "(-1)"
rlang::expr_deparse(x)
#> [1] "(-1)"
constructive::deparse_call(x)
#> Error in `constructive::deparse_call()`:
#> ! `call` must only be made of symbols and syntactic literals
#> Caused by error in `deparse_call_impl()` at constructive/R/deparse_call.R:105:5:
#> ! Found element of type 'double' and length '1':
#> -1

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


x <- call("fun", quote(expr = ))
base::deparse(x)
#> [1] "fun()"
rlang::expr_deparse(x)
#> [1] "fun()"
constructive::deparse_call(x) # this is wrong!
#> Error in `constructive::deparse_call()`:
#> ! `call` must only be made of symbols and syntactic literals
#> Caused by error in `deparse_call_impl()` at constructive/R/deparse_call.R:49:3:
#> ! Found empty symbol used as sole argument of a function:
#> as.call(list(quote(fun), quote(expr = )))

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

x <- call("!", quote(expr = ))
base::deparse(x)
#> [1] "!"
rlang::expr_deparse(x)
#> [1] "!"
constructive::deparse_call(x)
#> Error in `constructive::deparse_call()`:
#> ! `call` must only be made of symbols and syntactic literals
#> Caused by error in `deparse_call_impl()` at constructive/R/deparse_call.R:49:3:
#> ! Found empty symbol used as sole argument of a function:
#> as.call(list(quote(`!`), quote(expr = )))