Search code examples
rquote

How does `bquote` handle the dimensions of terms wrapped in `.()`?


I am confused about how bquote handles multidimensional terms (e.g., a matrix) wrapped in .().

Suppose I have the following X matrix.

# Set a seed exact draws.
set.seed(2023)

# Create a matrix.
X <- matrix(sample(1:100, 9), nrow = 3, ncol = 3)
X
#      [,1] [,2] [,3]
# [1,]   80   26   29
# [2,]   47   44   49
# [3,]   72   65   81

Wrapping X in .() does what I expect, i.e., it retains its dimensions.

# Wrap `X` in `.()`.
bquote(.(X))
#      [,1] [,2] [,3]
# [1,]   80   26   29
# [2,]   47   44   49
# [3,]   72   65   81

However, in the context of a function call, it seems that X is coerced to a vector. Despite this, R somehow still knows that X is a matrix, as seen in the output of the apply call below.

# Apply a function over rows.
output <- apply(X = X, MARGIN = 1, FUN = function(row) mean(row))
output
# [1] 45.00000 46.66667 72.66667

# Create an expression to apply a function over rows later.
expr <- bquote(apply(X = .(X), MARGIN = 1, FUN = function(row) mean(row)))
expr
# apply(X = c(80L, 47L, 72L, 26L, 44L, 65L, 29L, 49L, 81L), MARGIN = 1,
#    FUN = function(row) mean(row))

# Not necessary, but remove `X`.
rm(X, envir = .GlobalEnv)

# Evaluate the expression and compare it with the previous output.
eval(expr) == output
# [1] TRUE TRUE TRUE

How does R know that X is a matrix in the expr expression above?


Solution

  • bquote is irrelevant here. A simpler example:

    > expr <- call("is.matrix", matrix(1:4, 2L, 2L))
    > expr[[2L]]
    #      [,1] [,2]
    # [1,]    1    3
    # [2,]    2    4
    
    > eval(expr)
    # [1] TRUE
    
    > print(expr)
    # is.matrix(1:4)
    

    The argument of the call is a matrix, but print.default deparses it without attributes. deparse does the right thing:

    > writeLines(deparse(expr))
    # is.matrix(structure(1:4, dim = c(2L, 2L)))
    

    The reason is that deparse uses the "showAttributes" option by default:

    > (ctrl <- eval(formals(deparse)[["control"]]))
    # [1] "keepNA"         "keepInteger"    "niceNames"   "showAttributes"
    

    This character vector is converted by .deparseOpts to an integer whose bits indicate whether the respective options are turned on or off:

    > .deparseOpts(ctrl)
    # [1] 1093
    

    The header file Defn.h in R's sources defines the mapping:

    /* deparse option bits: change do_dump if more are added */
    
    #define KEEPINTEGER         1
    #define QUOTEEXPRESSIONS    2
    #define SHOWATTRIBUTES      4
    #define USESOURCE           8
    #define WARNINCOMPLETE      16
    #define DELAYPROMISES       32
    #define KEEPNA              64
    #define S_COMPAT            128
    #define HEXNUMERIC          256
    #define DIGITS17            512
    #define NICE_NAMES          1024
    /* common combinations of the above */
    #define SIMPLEDEPARSE       0
    #define DEFAULTDEPARSE      1089 /* KEEPINTEGER | KEEPNA | NICE_NAMES, used for calls */
    #define FORSOURCING         95 /* not DELAYPROMISES, used in edit.c */
    

    If you dig around in print.c, then you'll find (in the body of function PrintLanguage) that print.default uses the DEFAULTDEPARSE option for language objects. It has the SHOWATTRIBUTES bit turned off, and there is no option for the user to turn it on.

    Well, I've just asked in the R-devel mailing list (i.e., see here) if DEFAULTDEPARSE should be changed to 1093 to match the R level default.