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)
| x | Object to be tested. |
|---|
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. 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.
#> [1] FALSE#> [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#> [1] TRUE#> [1] TRUE# Primitive functions never appear in evaluation stacks: is_primitive(base::`[[`)#> [1] TRUE#> [1] TRUE#> [[1]] #> <frame 35> (34) #> expr: eval(expr, envir, enclos) #> env: [local 0x55ac99ef6ba0] #> #> [[2]] #> <frame 34> (24) #> expr: eval(expr, envir, enclos) #> env: [local 0x55ac99accdc0] #> #> [[3]] #> <frame 33> (24) #> expr: withVisible(eval(expr, envir, enclos)) #> env: [local 0x55ac99accb90] #> #> [[4]] #> <frame 32> (24) #> expr: withCallingHandlers(withVisible(eval(expr, envir, enclos)), warning = wHandler, <...> #> env: [local 0x55ac99acc2d0] #> #> [[5]] #> <frame 31> (30) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [local 0x55ac99acb850] #> #> [[6]] #> <frame 30> (29) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [local 0x55ac99acf1b0] #> #> [[7]] #> <frame 29> (28) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [local 0x55ac99acee68] #> #> [[8]] #> <frame 28> (27) #> expr: tryCatch(expr, error = function(e) { <...> #> env: [local 0x55ac99ace4c8] #> #> [[9]] #> <frame 27> (26) #> expr: try(f, silent = TRUE) #> env: [local 0x55ac99ace1f0] #> #> [[10]] #> <frame 26> (24) #> expr: handle(ev <- withCallingHandlers(withVisible(eval(expr, envir, <...> #> env: [local 0x55ac99acdfc0] #> #> [[11]] #> <frame 25> (24) #> expr: timing_fn(handle(ev <- withCallingHandlers(withVisible(eval(expr, <...> #> env: [local 0x55ac99acdee0] #> #> [[12]] #> <frame 24> (23) #> expr: evaluate_call(expr, parsed$src[[i]], envir = envir, enclos = enclos, <...> #> env: [frame 0x55ac99ae89a8] #> #> [[13]] #> <frame 23> (22) #> expr: evaluate::evaluate(code, child_env(env), new_device = TRUE) #> env: [frame 0x55ac9a6475f0] #> #> [[14]] #> <frame 22> (21) #> expr: downlit::evaluate_and_highlight(code, fig_save = fig_save_topic, <...> #> env: [frame 0x55ac9a64ece8] #> #> [[15]] #> <frame 21> (20) #> expr: highlight_examples(code, topic, env = env) #> env: [frame 0x55ac9a67e9e0] #> #> [[16]] #> <frame 20> (19) #> expr: run_examples(tags$tag_examples[[1]], env = new.env(parent = globalenv()), <...> #> env: [frame 0x55ac9a6f2430] #> #> [[17]] #> <frame 19> (17) #> expr: data_reference_topic(topic, pkg, examples = examples, run_dont_run = run_dont_run) #> env: [frame 0x55ac9e0cde50] #> #> [[18]] #> <frame 18> (17) #> expr: withCallingHandlers(data_reference_topic(topic, pkg, examples = examples, <...> #> env: [frame 0x55ac9e0ce588] #> #> [[19]] #> <frame 17> (16) #> expr: .f(.x[[i]], ...) #> env: [frame 0x55ac9e4b8a98] #> #> [[20]] #> <frame 16> (15) #> expr: purrr::map(topics, build_reference_topic, pkg = pkg, lazy = lazy, <...> #> env: [frame 0x55ac99cffc88] #> #> [[21]] #> <frame 15> (14) #> expr: build_reference(pkg, lazy = lazy, examples = examples, run_dont_run = run_dont_run, <...> #> env: [frame 0x55ac998fdff8] #> #> [[22]] #> <frame 14> (13) #> expr: build_site_local(pkg = pkg, examples = examples, run_dont_run = run_dont_run, <...> #> env: [frame 0x55ac97a84878] #> #> [[23]] #> <frame 13> (12) #> expr: pkgdown::build_site(...) #> env: [frame 0x55ac97a44830] #> #> [[24]] #> <frame 12> (0) #> expr: (function (..., crayon_enabled, crayon_colors, pkgdown_internet) <...> #> env: [frame 0x55ac9648ce00] #> #> [[25]] #> <frame 11> (0) #> expr: (function (what, args, quote = FALSE, envir = parent.frame()) <...> #> env: [frame 0x55ac964a0590] #> #> [[26]] #> <frame 10> (0) #> expr: do.call(do.call, c(readRDS("/tmp/RtmpfFDLtl/callr-fun-37b0379698bb"), <...> #> env: [frame 0x55ac976fb4c8] #> #> [[27]] #> <frame 9> (0) #> expr: saveRDS(do.call(do.call, c(readRDS("/tmp/RtmpfFDLtl/callr-fun-37b0379698bb"), <...> #> env: [frame 0x55ac976fb848] #> #> [[28]] #> <frame 8> (0) #> expr: withCallingHandlers({ <...> #> env: [frame 0x55ac976fc098] #> #> [[29]] #> <frame 7> (6) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x55ac976f88f8] #> #> [[30]] #> <frame 6> (5) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [frame 0x55ac976f8c40] #> #> [[31]] #> <frame 5> (2) #> expr: tryCatchList(expr, names[-nh], parentenv, handlers[-nh]) #> env: [frame 0x55ac976f8ff8] #> #> [[32]] #> <frame 4> (3) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x55ac976f95a8] #> #> [[33]] #> <frame 3> (2) #> expr: tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), <...> #> env: [frame 0x55ac976f98f0] #> #> [[34]] #> <frame 2> (1) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [frame 0x55ac976f9c38] #> #> [[35]] #> <frame 1> (0) #> expr: tryCatch(withCallingHandlers({ <...> #> env: [frame 0x55ac976fa370] #> #> [[36]] #> <frame 0> [global] #> expr: NULL #> env: [global] #> #> attr(,"class") #> [1] "ctxt_stack" "stack"#> [[1]] #> <frame 37> (35) #> expr: identity(ctxt_stack()) #> env: [local 0x55ac994b38e0] #> #> [[2]] #> <frame 36> (35) #> expr: identity(identity(ctxt_stack())) #> env: [local 0x55ac994b37c8] #> #> [[3]] #> <frame 35> (34) #> expr: eval(expr, envir, enclos) #> env: [local 0x55ac99ef6ba0] #> #> [[4]] #> <frame 34> (24) #> expr: eval(expr, envir, enclos) #> env: [local 0x55ac994b3608] #> #> [[5]] #> <frame 33> (24) #> expr: withVisible(eval(expr, envir, enclos)) #> env: [local 0x55ac994b33d8] #> #> [[6]] #> <frame 32> (24) #> expr: withCallingHandlers(withVisible(eval(expr, envir, enclos)), warning = wHandler, <...> #> env: [local 0x55ac994b2d80] #> #> [[7]] #> <frame 31> (30) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [local 0x55ac994b5cd0] #> #> [[8]] #> <frame 30> (29) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [local 0x55ac994b5950] #> #> [[9]] #> <frame 29> (28) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [local 0x55ac994b5608] #> #> [[10]] #> <frame 28> (27) #> expr: tryCatch(expr, error = function(e) { <...> #> env: [local 0x55ac994b4f40] #> #> [[11]] #> <frame 27> (26) #> expr: try(f, silent = TRUE) #> env: [local 0x55ac994b4c68] #> #> [[12]] #> <frame 26> (24) #> expr: handle(ev <- withCallingHandlers(withVisible(eval(expr, envir, <...> #> env: [local 0x55ac994b4aa8] #> #> [[13]] #> <frame 25> (24) #> expr: timing_fn(handle(ev <- withCallingHandlers(withVisible(eval(expr, <...> #> env: [local 0x55ac994b49c8] #> #> [[14]] #> <frame 24> (23) #> expr: evaluate_call(expr, parsed$src[[i]], envir = envir, enclos = enclos, <...> #> env: [frame 0x55ac994d4a68] #> #> [[15]] #> <frame 23> (22) #> expr: evaluate::evaluate(code, child_env(env), new_device = TRUE) #> env: [frame 0x55ac9a6475f0] #> #> [[16]] #> <frame 22> (21) #> expr: downlit::evaluate_and_highlight(code, fig_save = fig_save_topic, <...> #> env: [frame 0x55ac9a64ece8] #> #> [[17]] #> <frame 21> (20) #> expr: highlight_examples(code, topic, env = env) #> env: [frame 0x55ac9a67e9e0] #> #> [[18]] #> <frame 20> (19) #> expr: run_examples(tags$tag_examples[[1]], env = new.env(parent = globalenv()), <...> #> env: [frame 0x55ac9a6f2430] #> #> [[19]] #> <frame 19> (17) #> expr: data_reference_topic(topic, pkg, examples = examples, run_dont_run = run_dont_run) #> env: [frame 0x55ac9e0cde50] #> #> [[20]] #> <frame 18> (17) #> expr: withCallingHandlers(data_reference_topic(topic, pkg, examples = examples, <...> #> env: [frame 0x55ac9e0ce588] #> #> [[21]] #> <frame 17> (16) #> expr: .f(.x[[i]], ...) #> env: [frame 0x55ac9e4b8a98] #> #> [[22]] #> <frame 16> (15) #> expr: purrr::map(topics, build_reference_topic, pkg = pkg, lazy = lazy, <...> #> env: [frame 0x55ac99cffc88] #> #> [[23]] #> <frame 15> (14) #> expr: build_reference(pkg, lazy = lazy, examples = examples, run_dont_run = run_dont_run, <...> #> env: [frame 0x55ac998fdff8] #> #> [[24]] #> <frame 14> (13) #> expr: build_site_local(pkg = pkg, examples = examples, run_dont_run = run_dont_run, <...> #> env: [frame 0x55ac97a84878] #> #> [[25]] #> <frame 13> (12) #> expr: pkgdown::build_site(...) #> env: [frame 0x55ac97a44830] #> #> [[26]] #> <frame 12> (0) #> expr: (function (..., crayon_enabled, crayon_colors, pkgdown_internet) <...> #> env: [frame 0x55ac9648ce00] #> #> [[27]] #> <frame 11> (0) #> expr: (function (what, args, quote = FALSE, envir = parent.frame()) <...> #> env: [frame 0x55ac964a0590] #> #> [[28]] #> <frame 10> (0) #> expr: do.call(do.call, c(readRDS("/tmp/RtmpfFDLtl/callr-fun-37b0379698bb"), <...> #> env: [frame 0x55ac976fb4c8] #> #> [[29]] #> <frame 9> (0) #> expr: saveRDS(do.call(do.call, c(readRDS("/tmp/RtmpfFDLtl/callr-fun-37b0379698bb"), <...> #> env: [frame 0x55ac976fb848] #> #> [[30]] #> <frame 8> (0) #> expr: withCallingHandlers({ <...> #> env: [frame 0x55ac976fc098] #> #> [[31]] #> <frame 7> (6) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x55ac976f88f8] #> #> [[32]] #> <frame 6> (5) #> expr: tryCatchOne(expr, names, parentenv, handlers[[1L]]) #> env: [frame 0x55ac976f8c40] #> #> [[33]] #> <frame 5> (2) #> expr: tryCatchList(expr, names[-nh], parentenv, handlers[-nh]) #> env: [frame 0x55ac976f8ff8] #> #> [[34]] #> <frame 4> (3) #> expr: doTryCatch(return(expr), name, parentenv, handler) #> env: [frame 0x55ac976f95a8] #> #> [[35]] #> <frame 3> (2) #> expr: tryCatchOne(tryCatchList(expr, names[-nh], parentenv, handlers[-nh]), <...> #> env: [frame 0x55ac976f98f0] #> #> [[36]] #> <frame 2> (1) #> expr: tryCatchList(expr, classes, parentenv, handlers) #> env: [frame 0x55ac976f9c38] #> #> [[37]] #> <frame 1> (0) #> expr: tryCatch(withCallingHandlers({ <...> #> env: [frame 0x55ac976fa370] #> #> [[38]] #> <frame 0> [global] #> expr: NULL #> env: [global] #> #> attr(,"class") #> [1] "ctxt_stack" "stack"#> [1] TRUE#> [1] TRUEis_primitive_eager(base::`+`)#> [1] TRUE# However, primitives that operate on expressions, like quote() or # substitute(), are lazy: is_primitive_lazy(base::quote)#> [1] TRUE#> [1] TRUE