The R language defines two different types of functions: primitive functions, which are low-level, and closures, which are the regular kind of functions.

is_function(x)

is_closure(x)

is_primitive(x)

is_primitive_eager(x)

is_primitive_lazy(x)

Arguments

x

Object to be tested.

Details

Closures are functions written in R, named after the way their arguments are scoped within nested environments (see https://en.wikipedia.org/wiki/Closure_(computer_programming)). The root environment of the closure is called the closure environment. When closures are evaluated, a new environment called the evaluation frame is created with the closure environment as parent. This is where the body of the closure is evaluated. These closure frames appear on the evaluation stack (see ctxt_stack()), as opposed to primitive functions which do not necessarily have their own evaluation frame and never appear on the stack.

Primitive functions are more efficient than closures for two reasons. First, they are written entirely in fast low-level code. Second, the mechanism by which they are passed arguments is more efficient because they often do not need the full procedure of argument matching (dealing with positional versus named arguments, partial matching, etc). One practical consequence of the special way in which primitives are passed arguments is that they technically do not have formal arguments, and formals() will return NULL if called on a primitive function. See fn_fmls() for a function that returns a representation of formal arguments for primitive functions. Finally, primitive functions can either take arguments lazily, like R closures do, or evaluate them eagerly before being passed on to the C code. The former kind of primitives are called "special" in R terminology, while the latter is referred to as "builtin". is_primitive_eager() and is_primitive_lazy() allow you to check whether a primitive function evaluates arguments eagerly or lazily.

You will also encounter the distinction between primitive and internal functions in technical documentation. Like primitive functions, internal functions are defined at a low level and written in C. However, internal functions have no representation in the R language. Instead, they are called via a call to base::.Internal() within a regular closure. This ensures that they appear as normal R function objects: they obey all the usual rules of argument passing, and they appear on the evaluation stack as any other closures. As a result, fn_fmls() does not need to look in the .ArgsEnv environment to obtain a representation of their arguments, and there is no way of querying from R whether they are lazy ('special' in R terminology) or eager ('builtin').

You can call primitive functions with .Primitive() and internal functions with .Internal(). However, calling internal functions in a package is forbidden by CRAN's policy because they are considered part of the private API. They often assume that they have been called with correctly formed arguments, and may cause R to crash if you call them with unexpected objects.

Examples

# Primitive functions are not closures: is_closure(base::c)
#> [1] FALSE
is_primitive(base::c)
#> [1] TRUE
# On the other hand, internal functions are wrapped in a closure # and appear as such from the R side: is_closure(base::eval)
#> [1] TRUE
# Both closures and primitives are functions: is_function(base::c)
#> [1] TRUE
is_function(base::eval)
#> [1] TRUE
# Primitive functions never appear in evaluation stacks: is_primitive(base::`[[`)
#> [1] TRUE
is_primitive(base::list)
#> [1] TRUE
#> Warning: `ctxt_stack()` is soft-deprecated as of rlang 0.3.0. #> This warning is displayed once per session.
#> [[1]] #> <frame 37> (36) #> expr: eval(expr, envir, enclos) #> env: [local 0x8374c48] #> #> [[2]] #> <frame 36> (26) #> expr: eval(expr, envir, enclos) #> env: [local 0x8424f08] #> #> [[3]] #> <frame 35> (26) #> expr: withVisible(eval(expr, envir, enclos)) #> env: [local 0x842aa28] #> #> [[4]] #> <frame 34> (26) #> expr: withCallingHandlers(withVisible(eval(expr, envir, enclos)), warning = wHandler, <...> #> env: [local 0x8429bb8] #> #> [[5]] #> <frame 33> (32) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [local 0x8429640] #> #> [[6]] #> <frame 32> (31) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [local 0x8429288] #> #> [[7]] #> <frame 31> (30) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [local 0x8428f40] #> #> [[8]] #> <frame 30> (29) #> expr: tryCatch(expr, error = function(e) { <...> #> env: [local 0x842c5c8] #> #> [[9]] #> <frame 29> (28) #> expr: try(f, silent = TRUE) #> env: [local 0x842c398] #> #> [[10]] #> <frame 28> (26) #> expr: handle(ev <- withCallingHandlers(withVisible(eval(expr, envir, <...> #> env: [local 0x842bad8] #> #> [[11]] #> <frame 27> (26) #> expr: timing_fn(handle(ev <- withCallingHandlers(withVisible(eval(expr, <...> #> env: [local 0x842b9c0] #> #> [[12]] #> <frame 26> (25) #> expr: evaluate_call(expr, parsed$src[[i]], envir = envir, enclos = enclos, <...> #> env: [frame 0x84cb268] #> #> [[13]] #> <frame 25> (22) #> expr: evaluate::evaluate(code, env, new_device = TRUE) #> env: [frame 0x687df00] #> #> [[14]] #> <frame 24> (23) #> expr: force(code) #> env: [frame 0x687f588] #> #> [[15]] #> <frame 23> (22) #> expr: withr::with_options(list(crayon.enabled = getOption("crayon.enabled", <...> #> env: [frame 0x687cf68] #> #> [[16]] #> <frame 22> (21) #> expr: .f(code = .l[[1L]][[1L]], run = .l[[2L]][[1L]], show = .l[[3L]][[1L]], <...> #> env: [frame 0x687acc8] #> #> [[17]] #> <frame 21> (20) #> expr: purrr::pmap_chr(list(code = code, run = run, show = show), format_example_chunk, <...> #> env: [frame 0x8957150] #> #> [[18]] #> <frame 20> (18) #> expr: as_data.tag_examples(tags$tag_examples[[1]], env = new.env(parent = globalenv()), <...> #> env: [frame 0x85470a8] #> #> [[19]] #> <frame 19> (18) #> expr: as_data(tags$tag_examples[[1]], env = new.env(parent = globalenv()), <...> #> env: [frame 0x89c6c18] #> #> [[20]] #> <frame 18> (17) #> expr: data_reference_topic(topic, pkg, examples = examples, run_dont_run = run_dont_run) #> env: [frame 0x7e59c08] #> #> [[21]] #> <frame 17> (16) #> expr: .f(.x[[i]], ...) #> env: [frame 0x6e8ffc8] #> #> [[22]] #> <frame 16> (15) #> expr: purrr::map(topics, build_reference_topic, pkg = pkg, lazy = lazy, <...> #> env: [frame 0x5381c60] #> #> [[23]] #> <frame 15> (14) #> expr: build_reference(pkg, lazy = lazy, document = document, examples = examples, <...> #> env: [frame 0x1f2afd0] #> #> [[24]] #> <frame 14> (13) #> expr: build_site_local(pkg = pkg, examples = examples, document = document, <...> #> env: [frame 0x249c698] #> #> [[25]] #> <frame 13> (12) #> expr: pkgdown::build_site(...) #> env: [frame 0x249b380] #> #> [[26]] #> <frame 12> (0) #> expr: (function (..., crayon_enabled, crayon_colors, pkgdown_internet) <...> #> env: [frame 0x37d7790] #> #> [[27]] #> <frame 11> (0) #> expr: (function (what, args, quote = FALSE, envir = parent.frame()) <...> #> env: [frame 0x37d4920] #> #> [[28]] #> <frame 10> (0) #> expr: do.call(do.call, c(readRDS("/tmp/RtmpoQNQi0/file3f0b4c20076f"), <...> #> env: [frame 0x37d2760] #> #> [[29]] #> <frame 9> (0) #> expr: saveRDS(do.call(do.call, c(readRDS("/tmp/RtmpoQNQi0/file3f0b4c20076f"), <...> #> env: [frame 0x37d34f0] #> #> [[30]] #> <frame 8> (0) #> expr: withCallingHandlers({ <...> #> env: [frame 0x37d20f8] #> #> [[31]] #> <frame 7> (6) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x37cf480] #> #> [[32]] #> <frame 6> (5) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [frame 0x37cf7c8] #> #> [[33]] #> <frame 5> (2) #> expr: tryCatchList(expr, names[-nh], parentenv, handlers[-nh]) #> env: [frame 0x37cfb80] #> #> [[34]] #> <frame 4> (3) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x37d0130] #> #> [[35]] #> <frame 3> (2) #> expr: tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), <...> #> env: [frame 0x37d0478] #> #> [[36]] #> <frame 2> (1) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [frame 0x37cc990] #> #> [[37]] #> <frame 1> (0) #> expr: tryCatch(withCallingHandlers({ <...> #> env: [frame 0x37cd100] #> #> [[38]] #> <frame 0> [global] #> expr: NULL #> env: [global] #> #> attr(,"class") #> [1] "ctxt_stack" "stack"
# While closures do: identity(identity(ctxt_stack()))
#> [[1]] #> <frame 39> (37) #> expr: identity(ctxt_stack()) #> env: [local 0x61c8608] #> #> [[2]] #> <frame 38> (37) #> expr: identity(identity(ctxt_stack())) #> env: [local 0x61c84f0] #> #> [[3]] #> <frame 37> (36) #> expr: eval(expr, envir, enclos) #> env: [local 0x8374c48] #> #> [[4]] #> <frame 36> (26) #> expr: eval(expr, envir, enclos) #> env: [local 0x61c82c0] #> #> [[5]] #> <frame 35> (26) #> expr: withVisible(eval(expr, envir, enclos)) #> env: [local 0x61c8058] #> #> [[6]] #> <frame 34> (26) #> expr: withCallingHandlers(withVisible(eval(expr, envir, enclos)), warning = wHandler, <...> #> env: [local 0x61cb8a0] #> #> [[7]] #> <frame 33> (32) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [local 0x61cb328] #> #> [[8]] #> <frame 32> (31) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [local 0x61cafe0] #> #> [[9]] #> <frame 31> (30) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [local 0x61cac60] #> #> [[10]] #> <frame 30> (29) #> expr: tryCatch(expr, error = function(e) { <...> #> env: [local 0x61ca528] #> #> [[11]] #> <frame 29> (28) #> expr: try(f, silent = TRUE) #> env: [local 0x61ca288] #> #> [[12]] #> <frame 28> (26) #> expr: handle(ev <- withCallingHandlers(withVisible(eval(expr, envir, <...> #> env: [local 0x61ca100] #> #> [[13]] #> <frame 27> (26) #> expr: timing_fn(handle(ev <- withCallingHandlers(withVisible(eval(expr, <...> #> env: [local 0x61ca020] #> #> [[14]] #> <frame 26> (25) #> expr: evaluate_call(expr, parsed$src[[i]], envir = envir, enclos = enclos, <...> #> env: [frame 0x621a810] #> #> [[15]] #> <frame 25> (22) #> expr: evaluate::evaluate(code, env, new_device = TRUE) #> env: [frame 0x687df00] #> #> [[16]] #> <frame 24> (23) #> expr: force(code) #> env: [frame 0x687f588] #> #> [[17]] #> <frame 23> (22) #> expr: withr::with_options(list(crayon.enabled = getOption("crayon.enabled", <...> #> env: [frame 0x687cf68] #> #> [[18]] #> <frame 22> (21) #> expr: .f(code = .l[[1L]][[1L]], run = .l[[2L]][[1L]], show = .l[[3L]][[1L]], <...> #> env: [frame 0x687acc8] #> #> [[19]] #> <frame 21> (20) #> expr: purrr::pmap_chr(list(code = code, run = run, show = show), format_example_chunk, <...> #> env: [frame 0x8957150] #> #> [[20]] #> <frame 20> (18) #> expr: as_data.tag_examples(tags$tag_examples[[1]], env = new.env(parent = globalenv()), <...> #> env: [frame 0x85470a8] #> #> [[21]] #> <frame 19> (18) #> expr: as_data(tags$tag_examples[[1]], env = new.env(parent = globalenv()), <...> #> env: [frame 0x89c6c18] #> #> [[22]] #> <frame 18> (17) #> expr: data_reference_topic(topic, pkg, examples = examples, run_dont_run = run_dont_run) #> env: [frame 0x7e59c08] #> #> [[23]] #> <frame 17> (16) #> expr: .f(.x[[i]], ...) #> env: [frame 0x6e8ffc8] #> #> [[24]] #> <frame 16> (15) #> expr: purrr::map(topics, build_reference_topic, pkg = pkg, lazy = lazy, <...> #> env: [frame 0x5381c60] #> #> [[25]] #> <frame 15> (14) #> expr: build_reference(pkg, lazy = lazy, document = document, examples = examples, <...> #> env: [frame 0x1f2afd0] #> #> [[26]] #> <frame 14> (13) #> expr: build_site_local(pkg = pkg, examples = examples, document = document, <...> #> env: [frame 0x249c698] #> #> [[27]] #> <frame 13> (12) #> expr: pkgdown::build_site(...) #> env: [frame 0x249b380] #> #> [[28]] #> <frame 12> (0) #> expr: (function (..., crayon_enabled, crayon_colors, pkgdown_internet) <...> #> env: [frame 0x37d7790] #> #> [[29]] #> <frame 11> (0) #> expr: (function (what, args, quote = FALSE, envir = parent.frame()) <...> #> env: [frame 0x37d4920] #> #> [[30]] #> <frame 10> (0) #> expr: do.call(do.call, c(readRDS("/tmp/RtmpoQNQi0/file3f0b4c20076f"), <...> #> env: [frame 0x37d2760] #> #> [[31]] #> <frame 9> (0) #> expr: saveRDS(do.call(do.call, c(readRDS("/tmp/RtmpoQNQi0/file3f0b4c20076f"), <...> #> env: [frame 0x37d34f0] #> #> [[32]] #> <frame 8> (0) #> expr: withCallingHandlers({ <...> #> env: [frame 0x37d20f8] #> #> [[33]] #> <frame 7> (6) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x37cf480] #> #> [[34]] #> <frame 6> (5) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [frame 0x37cf7c8] #> #> [[35]] #> <frame 5> (2) #> expr: tryCatchList(expr, names[-nh], parentenv, handlers[-nh]) #> env: [frame 0x37cfb80] #> #> [[36]] #> <frame 4> (3) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x37d0130] #> #> [[37]] #> <frame 3> (2) #> expr: tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), <...> #> env: [frame 0x37d0478] #> #> [[38]] #> <frame 2> (1) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [frame 0x37cc990] #> #> [[39]] #> <frame 1> (0) #> expr: tryCatch(withCallingHandlers({ <...> #> env: [frame 0x37cd100] #> #> [[40]] #> <frame 0> [global] #> expr: NULL #> env: [global] #> #> attr(,"class") #> [1] "ctxt_stack" "stack"
# Many primitive functions evaluate arguments eagerly: is_primitive_eager(base::c)
#> [1] TRUE
is_primitive_eager(base::list)
#> [1] TRUE
is_primitive_eager(base::`+`)
#> [1] TRUE
# However, primitives that operate on expressions, like quote() or # substitute(), are lazy: is_primitive_lazy(base::quote)
#> [1] TRUE
is_primitive_lazy(base::substitute)
#> [1] TRUE