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 0x4eee368] #> #> [[2]] #> <frame 36> (26) #> expr: eval(expr, envir, enclos) #> env: [local 0x497ec10] #> #> [[3]] #> <frame 35> (26) #> expr: withVisible(eval(expr, envir, enclos)) #> env: [local 0x497e9e0] #> #> [[4]] #> <frame 34> (26) #> expr: withCallingHandlers(withVisible(eval(expr, envir, enclos)), warning = wHandler, <...> #> env: [local 0x497e3f8] #> #> [[5]] #> <frame 33> (32) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [local 0x4983b98] #> #> [[6]] #> <frame 32> (31) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [local 0x4983818] #> #> [[7]] #> <frame 31> (30) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [local 0x49834d0] #> #> [[8]] #> <frame 30> (29) #> expr: tryCatch(expr, error = function(e) { <...> #> env: [local 0x4982158] #> #> [[9]] #> <frame 29> (28) #> expr: try(f, silent = TRUE) #> env: [local 0x4981f28] #> #> [[10]] #> <frame 28> (26) #> expr: handle(ev <- withCallingHandlers(withVisible(eval(expr, envir, <...> #> env: [local 0x4987af0] #> #> [[11]] #> <frame 27> (26) #> expr: timing_fn(handle(ev <- withCallingHandlers(withVisible(eval(expr, <...> #> env: [local 0x4987a10] #> #> [[12]] #> <frame 26> (25) #> expr: evaluate_call(expr, parsed$src[[i]], envir = envir, enclos = enclos, <...> #> env: [frame 0x49a8698] #> #> [[13]] #> <frame 25> (22) #> expr: evaluate::evaluate(code, env, new_device = TRUE) #> env: [frame 0x576c7c0] #> #> [[14]] #> <frame 24> (23) #> expr: force(code) #> env: [frame 0x576afb0] #> #> [[15]] #> <frame 23> (22) #> expr: withr::with_options(list(crayon.enabled = getOption("crayon.enabled", <...> #> env: [frame 0x576d560] #> #> [[16]] #> <frame 22> (21) #> expr: .f(code = .l[[c(1L, 1L)]], run = .l[[c(2L, 1L)]], show = .l[[c(3L, <...> #> env: [frame 0x576fe58] #> #> [[17]] #> <frame 21> (20) #> expr: purrr::pmap_chr(list(code = code, run = run, show = show), format_example_chunk, <...> #> env: [frame 0x5774f78] #> #> [[18]] #> <frame 20> (18) #> expr: as_data.tag_examples(tags$tag_examples[[1]], env = new.env(parent = globalenv()), <...> #> env: [frame 0x5840920] #> #> [[19]] #> <frame 19> (18) #> expr: as_data(tags$tag_examples[[1]], env = new.env(parent = globalenv()), <...> #> env: [frame 0x583ff10] #> #> [[20]] #> <frame 18> (17) #> expr: data_reference_topic(topic, pkg, examples = examples, run_dont_run = run_dont_run) #> env: [frame 0x72da008] #> #> [[21]] #> <frame 17> (16) #> expr: .f(.x[[i]], ...) #> env: [frame 0x752a460] #> #> [[22]] #> <frame 16> (15) #> expr: purrr::map(topics, build_reference_topic, pkg = pkg, lazy = lazy, <...> #> env: [frame 0x58c61d0] #> #> [[23]] #> <frame 15> (14) #> expr: build_reference(pkg, lazy = lazy, document = document, examples = examples, <...> #> env: [frame 0x17c9338] #> #> [[24]] #> <frame 14> (13) #> expr: build_site_local(pkg = pkg, examples = examples, document = document, <...> #> env: [frame 0x2001c88] #> #> [[25]] #> <frame 13> (12) #> expr: pkgdown::build_site(...) #> env: [frame 0x20896e8] #> #> [[26]] #> <frame 12> (0) #> expr: (function (..., crayon_enabled, crayon_colors, pkgdown_internet) <...> #> env: [frame 0x2d518e0] #> #> [[27]] #> <frame 11> (0) #> expr: (function (what, args, quote = FALSE, envir = parent.frame()) <...> #> env: [frame 0x2d4ea70] #> #> [[28]] #> <frame 10> (0) #> expr: do.call(do.call, c(readRDS("/tmp/RtmpjyQrSc/file3f2e5de9df88"), <...> #> env: [frame 0x2d4a990] #> #> [[29]] #> <frame 9> (0) #> expr: saveRDS(do.call(do.call, c(readRDS("/tmp/RtmpjyQrSc/file3f2e5de9df88"), <...> #> env: [frame 0x2d4b720] #> #> [[30]] #> <frame 8> (0) #> expr: withCallingHandlers({ <...> #> env: [frame 0x2d4a328] #> #> [[31]] #> <frame 7> (6) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x2d476b0] #> #> [[32]] #> <frame 6> (5) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [frame 0x2d479f8] #> #> [[33]] #> <frame 5> (2) #> expr: tryCatchList(expr, names[-nh], parentenv, handlers[-nh]) #> env: [frame 0x2d47db0] #> #> [[34]] #> <frame 4> (3) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x2d48360] #> #> [[35]] #> <frame 3> (2) #> expr: tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), <...> #> env: [frame 0x2d486a8] #> #> [[36]] #> <frame 2> (1) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [frame 0x2d44bc0] #> #> [[37]] #> <frame 1> (0) #> expr: tryCatch(withCallingHandlers({ <...> #> env: [frame 0x2d45330] #> #> [[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 0x20816e0] #> #> [[2]] #> <frame 38> (37) #> expr: identity(identity(ctxt_stack())) #> env: [local 0x2081590] #> #> [[3]] #> <frame 37> (36) #> expr: eval(expr, envir, enclos) #> env: [local 0x4eee368] #> #> [[4]] #> <frame 36> (26) #> expr: eval(expr, envir, enclos) #> env: [local 0x2081398] #> #> [[5]] #> <frame 35> (26) #> expr: withVisible(eval(expr, envir, enclos)) #> env: [local 0x2081168] #> #> [[6]] #> <frame 34> (26) #> expr: withCallingHandlers(withVisible(eval(expr, envir, enclos)), warning = wHandler, <...> #> env: [local 0x1aa2648] #> #> [[7]] #> <frame 33> (32) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [local 0x1aa1fb8] #> #> [[8]] #> <frame 32> (31) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [local 0x1aa1b90] #> #> [[9]] #> <frame 31> (30) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [local 0x1aa16c0] #> #> [[10]] #> <frame 30> (29) #> expr: tryCatch(expr, error = function(e) { <...> #> env: [local 0x1aa0e70] #> #> [[11]] #> <frame 29> (28) #> expr: try(f, silent = TRUE) #> env: [local 0x1aa8cb0] #> #> [[12]] #> <frame 28> (26) #> expr: handle(ev <- withCallingHandlers(withVisible(eval(expr, envir, <...> #> env: [local 0x1aa8b28] #> #> [[13]] #> <frame 27> (26) #> expr: timing_fn(handle(ev <- withCallingHandlers(withVisible(eval(expr, <...> #> env: [local 0x1aa8a48] #> #> [[14]] #> <frame 26> (25) #> expr: evaluate_call(expr, parsed$src[[i]], envir = envir, enclos = enclos, <...> #> env: [frame 0x1d6a468] #> #> [[15]] #> <frame 25> (22) #> expr: evaluate::evaluate(code, env, new_device = TRUE) #> env: [frame 0x576c7c0] #> #> [[16]] #> <frame 24> (23) #> expr: force(code) #> env: [frame 0x576afb0] #> #> [[17]] #> <frame 23> (22) #> expr: withr::with_options(list(crayon.enabled = getOption("crayon.enabled", <...> #> env: [frame 0x576d560] #> #> [[18]] #> <frame 22> (21) #> expr: .f(code = .l[[c(1L, 1L)]], run = .l[[c(2L, 1L)]], show = .l[[c(3L, <...> #> env: [frame 0x576fe58] #> #> [[19]] #> <frame 21> (20) #> expr: purrr::pmap_chr(list(code = code, run = run, show = show), format_example_chunk, <...> #> env: [frame 0x5774f78] #> #> [[20]] #> <frame 20> (18) #> expr: as_data.tag_examples(tags$tag_examples[[1]], env = new.env(parent = globalenv()), <...> #> env: [frame 0x5840920] #> #> [[21]] #> <frame 19> (18) #> expr: as_data(tags$tag_examples[[1]], env = new.env(parent = globalenv()), <...> #> env: [frame 0x583ff10] #> #> [[22]] #> <frame 18> (17) #> expr: data_reference_topic(topic, pkg, examples = examples, run_dont_run = run_dont_run) #> env: [frame 0x72da008] #> #> [[23]] #> <frame 17> (16) #> expr: .f(.x[[i]], ...) #> env: [frame 0x752a460] #> #> [[24]] #> <frame 16> (15) #> expr: purrr::map(topics, build_reference_topic, pkg = pkg, lazy = lazy, <...> #> env: [frame 0x58c61d0] #> #> [[25]] #> <frame 15> (14) #> expr: build_reference(pkg, lazy = lazy, document = document, examples = examples, <...> #> env: [frame 0x17c9338] #> #> [[26]] #> <frame 14> (13) #> expr: build_site_local(pkg = pkg, examples = examples, document = document, <...> #> env: [frame 0x2001c88] #> #> [[27]] #> <frame 13> (12) #> expr: pkgdown::build_site(...) #> env: [frame 0x20896e8] #> #> [[28]] #> <frame 12> (0) #> expr: (function (..., crayon_enabled, crayon_colors, pkgdown_internet) <...> #> env: [frame 0x2d518e0] #> #> [[29]] #> <frame 11> (0) #> expr: (function (what, args, quote = FALSE, envir = parent.frame()) <...> #> env: [frame 0x2d4ea70] #> #> [[30]] #> <frame 10> (0) #> expr: do.call(do.call, c(readRDS("/tmp/RtmpjyQrSc/file3f2e5de9df88"), <...> #> env: [frame 0x2d4a990] #> #> [[31]] #> <frame 9> (0) #> expr: saveRDS(do.call(do.call, c(readRDS("/tmp/RtmpjyQrSc/file3f2e5de9df88"), <...> #> env: [frame 0x2d4b720] #> #> [[32]] #> <frame 8> (0) #> expr: withCallingHandlers({ <...> #> env: [frame 0x2d4a328] #> #> [[33]] #> <frame 7> (6) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x2d476b0] #> #> [[34]] #> <frame 6> (5) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [frame 0x2d479f8] #> #> [[35]] #> <frame 5> (2) #> expr: tryCatchList(expr, names[-nh], parentenv, handlers[-nh]) #> env: [frame 0x2d47db0] #> #> [[36]] #> <frame 4> (3) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x2d48360] #> #> [[37]] #> <frame 3> (2) #> expr: tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), <...> #> env: [frame 0x2d486a8] #> #> [[38]] #> <frame 2> (1) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [frame 0x2d44bc0] #> #> [[39]] #> <frame 1> (0) #> expr: tryCatch(withCallingHandlers({ <...> #> env: [frame 0x2d45330] #> #> [[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