Search code examples
rdata-structuresfirst-class-functions

Why are functions only sometimes first class variables in R? Using them from built-in data structures causes "Error: attempt to apply non-function"


I am trying to understand how first class functions work in R. I had understood that functions were first class in R, but was sorely disappointed when applying that understanding. When a function is saved to a list, whether that be as an ordinary list, a vector or a dictionary style list or vector, it is no longer callable, leading to the following error:

Error: attempt to apply non-function

e.g.

print_func <- function() {
  print('hi')
}
print_func()
[1] "hi"

my_list = list(print_func)
my_list[0]()
Error: attempt to apply non-function

my_vector = c(print_func)
my_vector[0]()
Error: attempt to apply non-function

my_map <- c("a" = print_func)
my_map["a"]()
Error: attempt to apply non-function

So why is this? Does R not actually treat functions as first class members in all cases, or is there another reason why this occurs?

I see that R vectors also do unexpected things (for me - perhaps not for experienced R users) to nested arrays:

nested_vector <- c("a" = c("b" = 1))
nested_vector["a"]
<NA> 
  NA
nested_vector["a.b"]
a.b 
  1

Here it makes sense to me that "a.b" might reference the sub-member of the key "b" under the key "a". But apparently that logic goes out the window when trying to call the upper level key "a".


Solution

  • R is 1-based; so, you refer to the first element of a vector using index 1 (and not 0 like in python).

    There are two approaches to accessing list elements:

    • accessing list elements while keeping a list (return a list containing the desired elements)
    • pulling an element out of a list

    In the first case, the subsetting is done using a single pair of brackets ([]) and you will always get a list back. Note that this is different from python where you get a list only if you select more than one element (lst = [fun1, fun2]; lst[0] return fun1 and not a one-element list like R while lst[0:2] returns a list).

    In the second approach, the subsetting is done using a double pair of brackets ([[]]). you basically pull an element completely out of a list; more like subsetting one element out of a list in python.

    print_func <- function() {
      print('hi')
    }
    print_func()
    
    
    my_list = list(print_func)
    
    mode(my_list[1])      # return a list (not a function); so, it's not callable
    [1] "list"
    mode(my_list[[1]])    # return a function; so, it's callable
    [1] "function"
    my_list[1]()          # error
    my_list[[1]]()        # works
    [1] "hi"
    # 
    my_vector = c(print_func)
    mode(my_vector)         # a list, so, not callable
    [1] "list"
    my_vector[1]()          # error because it returns a list and not a function
    my_vector[[1]]()        # works
    [1] "hi"
    

    When subsetting with names, the same logic of single and double pair of brackets applies

    my_map <- c("a" = print_func)
    mode(my_map)            # list, so, not callable
    [1] "list"
    my_map["a"]()           # error
    my_map[["a"]]()         # works
    [1] "hi"