# Good
add_row()
permute()
# Bad
row_adder()
permutation()3 Functions
3.1 Naming
As well as following the general advice for object names in Section 2.1, strive to use verbs for function names:
3.2 Anonymous functions
Use the new lambda syntax: \(x) x + 1 when writing short anonymous functions (i.e. when you define a function in an argument without giving it an explicit name).
# Good
map(xs, \(x) mean((x + 5)^2))
map(xs, function(x) mean((x + 5)^2))
# Bad
map(xs, ~ mean((.x + 5)^2))Don’t use \() for multi-line functions:
# Good
map(xs, function(x) {
mean((x + 5)^2)
})
# Bad
map(xs, \(x) {
mean((x + 5)^2)
})Or when creating named functions:
# Good
cv <- function(x) {
sd(x) / mean(x)
}
# Bad
cv <- \(x) sd(x) / mean(x)Avoid using \() in a pipe, and remember to use informative argument names.
3.3 Multi-line function definitions
There are two options if the function name and definition can’t fit on a single line. In both cases, each argument goes on its own line; the difference is how deep you indent it and where you put ) and {:
Single-indent: indent the argument name with a single indent (i.e. two spaces). The trailing
)and leading{go on a new line.# Good long_function_name <- function( a = "a long argument", b = "another argument", c = "another long argument" ) { # As usual code is indented by two spaces. }Hanging-indent: indent the argument name to match the opening
(offunction. The trailing)and leading{go on the same line as the last argument.# Good long_function_name <- function(a = "a long argument", b = "another argument", c = "another long argument") { # As usual code is indented by two spaces. }
These styles are designed to clearly separate the function definition from its body.
# Bad
long_function_name <- function(a = "a long argument",
b = "another argument",
c = "another long argument") {
# Here it's hard to spot where the definition ends and the
# code begins, and to see all three function arguments
}If a function argument can’t fit on a single line, this is a sign you should rework the argument to keep it short and sweet.
3.4 S7
In S7, the method definition can be long because the function name is replaced by a method call that specifies the generic and dispatch classes. In this case we recommend the single-indent style.
method(from_provider, list(openai_provider, class_any)) <- function(
provider,
x,
...,
error_call = caller_env()
) {
...
}If the method definition is too long to fit on one line, use the usual rules to spread the method arguments across multiple lines:
method(
from_provider,
list(openai_provider, class_any, a_very_long_class_name)
) <- function(
provider,
x,
...,
error_call = caller_env()
) {
...
}3.5 return()
Only use return() for early returns. Otherwise, rely on R to return the result of the last evaluated expression.
# Good
find_abs <- function(x) {
if (x > 0) {
return(x)
}
x * -1
}
add_two <- function(x, y) {
x + y
}
# Bad
add_two <- function(x, y) {
return(x + y)
}Return statements should always be on their own line because they have important effects on the control flow. See also control flow modifiers.
# Good
find_abs <- function(x) {
if (x > 0) {
return(x)
}
x * -1
}
# Bad
find_abs <- function(x) {
if (x > 0) return(x)
x * -1
}If your function is called primarily for its side-effects (like printing, plotting, or saving to disk), it should return the first argument invisibly. This makes it possible to use the function as part of a pipe. print methods should usually do this, like this example from httr:
print.url <- function(x, ...) {
cat("Url: ", build_url(x), "\n", sep = "")
invisible(x)
}
3.6 Comments
In code, use comments to explain the “why” not the “what” or “how”. Each line of a comment should begin with the comment symbol and a single space:
#.Comments should be in sentence case, and only end with a full stop if they contain at least two sentences: