object of type 'closure' is not(?) subsettable

R Error messages: A visual overview

R Error message: object of type 'closure' is not subsettable

A common error in R is object of type 'closure' is not subsettable. This message means that you have a variable which represents a function, and you’re mistakenly using square brackets to try and subset it, thinking that it represents a data.frame or vector or something e.g.

# `mean` here refers the built-in base R function
mean[1:3]
## Error in mean[1:3]: object of type 'closure' is not subsettable

In general if you get this error it means you’re trying to subset a function (or somehow access an item within a function using $) which (most of the time) doesn’t make much sense in normal R code.

What if objects of type ‘closure’ were subsettable?

While riffing on a joke tweet, Luke Smith wondered on twitter if there was any actual case for make closures subsettable:

The rest of this post is my idea for something vaguely useful you could do with subsetting a function/closure in R.

Using subset syntax to manipulate formal arguments of functions

  • Aim: manipulate formal arguments of a function using [ ] or $ syntax
  • E.g: Use rnorm$mean <- 100 to change the default mean for the rnorm() function.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Tell R that we want to make `[`  and `$` generic
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`[`   <- function(...) {UseMethod("["  )}
`[<-` <- function(...) {UseMethod("[<-")}
`$`   <- function(...) {UseMethod("$"  )}
`$<-` <- function(...) {UseMethod("$<-")}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Enable subset with [] on a function so we can:
#' Determine the current default value for a given formal argument
#' 
#' e.g.   rnorm['mean']
#'
#' @param func function
#' @param arg_name name of formal argument (character)
#'
#' @return current value of formal argument (or warning if not exists)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`[.function` <- `[.closure` <- function(func, arg_name) {
  if (arg_name %in% names(formals(func))) {
    formals(func)[[arg_name]]
  } else {
    warning("No such formal argument: ", arg_name)
  }
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Same as above except allow:   rnorm$mean
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`$.function` <- `$.closure` <- function(func, arg_name) {
  func[as.character(substitute(arg_name))]
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Enable subset assignment on a function so we can:
# Set the given formal argument of the function to the new default value
#
#' e.g.   rnorm['mean'] <- 100
#'
#' @param func function
#' @param arg_name name of formal argument (character)
#' @param value new default value for formal argument
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`[<-.function` <- `[<-.closure` <- function(func, arg_name, value) {
  fargs             <- formals(func)
  fargs[[arg_name]] <- value
  formals(func)     <- fargs
  
  func
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Same as above except allow:   rnorm$mean <- 100
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`$<-.function` <- `$<-.closure` <- function(func, arg_name, value) {
  func[as.character(substitute(arg_name))] <- value
  func
}

Example: changing the default values of rnorm() using function subsetting

# Find the default 'mean' for this function using '$' syntax
rnorm$mean
## [1] 0

# Set new values for the default `n` and `mean`
rnorm$n     <- 5
rnorm$mean <- 100

# Looking at the function, we can see that the default arguments have been changed
rnorm
## function (n = 5, mean = 100, sd = 1) 
## .Call(C_rnorm, n, mean, sd)
## <environment: namespace:stats>

# And calling it, runs it with the new defaults
rnorm()
## [1]  99.37355 100.18364  99.16437 101.59528 100.32951

Notes

  • You can make objects of type ‘closure’ subsettable.
  • If ‘closure’ objects are made subsettable, then you can reason about them and manipulate their arguments and code in a (possibly) intuitive manner.
  • You’d probably never want to actually do this because it’s going to be super unsafe behaviour most of the time i.e. 99.9999999% of the cases, the user expects an error if they try and subset a function, not some weird thing to do with formal arguments as I’ve defined here.
  • TL;DR - Fun, but don’t do it (unless you really want to)

Summary - Please don’t