Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion functions.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ add_two <- function(x, y) {
}
```

Return statements should always be on their own line because they have important effects on the control flow. See also [inline statements](#inline-statements).
Return statements should always be on their own line because they have important effects on the control flow. See also [control flow modifiers](#control-flow-modifiers).

```{r}
# Good
Expand Down
288 changes: 176 additions & 112 deletions syntax.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -292,24 +292,85 @@ The only exception is in functions that capture side-effects:
output <- capture.output(x <- f())
```

### Long function calls

## Control flow
Strive to limit your code to 80 characters per line. This fits comfortably on a
printed page with a reasonably sized font. If you find yourself running out of
room, this is a good indication that you should encapsulate some of the work in
a separate function or use early returns to reduce the nesting in your code.

If a function call is too long to fit on a single line, use one line each for
the function name, each argument, and the closing `)`.
This makes the code easier to read and to change later.

```{r}
# Good
do_something_very_complicated(
something = "that",
requires = many,
arguments = "some of which may be long"
)

# Bad
do_something_very_complicated("that", requires, many, arguments,
"some of which may be long"
)
```

As described under [Named arguments](#argument-names), you can omit the argument names
for very common arguments (i.e. for arguments that are used in almost every
invocation of the function). If this introduces a large disparity between the line lengths, you may want to supply names anyway:

```{r}
# Good
my_function(
x,
long_argument_name,
extra_argument_a = 10,
extra_argument_b = c(1, 43, 390, 210209)
)

# Also good
my_function(
x = x,
y = long_argument_name,
extra_argument_a = 10,
extra_argument_b = c(1, 43, 390, 210209)
)
```

You may place multiple unnamed arguments on the same line if they are closely
related to each other. A common example of this is creating strings
with `paste()`. In such cases, it's often beneficial to match one line of code
to one line of output.

### Code blocks {#indenting}
```{r}
# Good
paste0(
"Requirement: ", requires, "\n",
"Result: ", result, "\n"
)

Curly braces, `{}`, define the most important hierarchy of R code. To make this
hierarchy easy to see:
# Bad
paste0(
"Requirement: ", requires,
"\n", "Result: ",
result, "\n")
```

## Braced expressions {#braced-expressions}

Braced expressions, `{}`, define the most important hierarchy of R code, allowing you to group multiple R expressions together into a single expression. The most common places to use braced expressions are in function definitions, control flow, and in certain function calls (e.g. `tryCatch()` and `test_that()`).

To make this hierarchy easy to see:

* `{` should be the last character on the line. Related code (e.g., an `if`
clause, a function declaration, a trailing comma, ...) must be on the same
line as the opening brace.
* `{` should be the last character on the line.
Related code (e.g., an `if` clause, a function declaration, a trailing comma, ...) must be on the same line as the opening brace.

* The contents should be indented by two spaces.

* `}` should be the first character on the line.

It is occassionally useful to have an empty curly braces block, in which case it should be written `{}`.

```{r}
# Good
if (y < 0 && debug) {
Expand Down Expand Up @@ -340,8 +401,6 @@ tryCatch(
}
)

while (waiting_for_something()) {}

# Bad
if (y < 0 && debug) {
message("Y is negative")
Expand All @@ -355,52 +414,120 @@ if (y == 0)
message("x is negative or zero")
}
} else { y ^ x }
```

It is occasionally useful to have empty braced expressions, in which case it should be written `{}`, with no intervening space.

```{r}
# Good
function(...) {}

while (waiting_for_something()) { }
while (waiting_for_something()) {
# Bad
function(...) { }
function(...) {

}
```

## Control flow

### Loops

R defines three types of looping constructs: `for`, `while`, and `repeat` loops.

* The body of a loop must be a braced expression.

```{r}
# Good
for (i in seq) {
x[i] <- x[i] + 1
}

while (waiting_for_something()) {
cat("Still waiting...")
}

# Bad
for (i in seq) x[i] <- x[i] + 1

while (waiting_for_something()) cat("Still waiting...")
```

* It is occasionally useful to use a `while` loop with an empty braced expression body to wait. As mentioned in [Braced expressions](#braced-expressions), there should be no space within the `{}`.

### If statements

* When present, `else` should be on the same line as `}`.
* A single line if statement must never contain braced expressions. You can use
single line if statements for very simple statements that don't have
side-effects and don't modify the control flow.

* `&` and `|` should never be used inside of an `if` clause because they can
return vectors. Always use `&&` and `||` instead.
```{r}
# Good
message <- if (x > 10) "big" else "small"

# Bad
message <- if (x > 10) { "big" } else { "small" }

* NB: `ifelse(x, a, b)` is not a drop-in replacement for `if (x) a else b`.
`ifelse()` is vectorised (i.e. if `length(x) > 1`, then `a` and `b` will be
recycled to match) and it is eager (i.e. both `a` and `b` will always be
evaluated).
if (x > 0) message <- "big" else message <- "small"

If you want to rewrite a simple but lengthy `if` block:
if (x > 0) return(x)
```

* A multiline if statement must contain braced expressions.

```{r}
# Good
if (x > 10) {
x * 2
}

if (x > 10) {
message <- "big"
x * 2
} else {
message <- "small"
x * 3
}

# Bad
if (x > 10)
x * 2

# In particular, this if statement will only parse when wrapped in a braced
# expression or call
{
if (x > 10)
x * 2
else
x * 3
}
```

Just write it all on one line:
* When present, `else` should be on the same line as `}`.

* Avoid implicit type coercion (e.g. from numeric to logical) in the condition of an if statement:

```{r}
message <- if (x > 10) "big" else "small"
# Good
if (length(x) > 0) {
# do something
}

# Bad
if (length(x)) {
# do something
}
```

### Inline statements {#inline-statements}
::: {.callout-note}
`&` and `|` should never be used inside of an `if` clause because they can return vectors. Always use `&&` and `||` instead.
:::

It's ok to drop the curly braces for very simple statements that fit on one line, as long as they don't have side-effects.
::: {.callout-note}
`ifelse(x, a, b)` is not a drop-in replacement for `if (x) a else b`. `ifelse()` is vectorised (i.e. if `length(x) > 1`, then `a` and `b` will be recycled to match) and it is eager (i.e. both `a` and `b` will always be evaluated).
:::

```{r}
# Good
y <- 10
x <- if (y < 20) "Too low" else "Too high"
```
### Control flow modifiers {#control-flow-modifiers}

Function calls that affect control flow (like `return()`, `stop()` or `continue`) should always go in their own `{}` block:
Syntax that affects control flow (like `return()`, `stop()`, `break`, or `next`) should always go in their own `{}` block:

```{r}
# Good
Expand All @@ -415,31 +542,22 @@ find_abs <- function(x) {
x * -1
}

for (x in xs) {
if (is_done(x)) {
break
}
}

# Bad
if (y < 0) stop("Y is negative")

if (y < 0)
stop("Y is negative")

find_abs <- function(x) {
if (x > 0) return(x)
x * -1
}
```

### Implicit type coercion

Avoid implicit type coercion (e.g. from numeric to logical) in `if` statements:

```{r}
# Good
if (length(x) > 0) {
# do something
}

# Bad
if (length(x)) {
# do something
for (x in xs) {
if (is_done(x)) break
}
```

Expand Down Expand Up @@ -472,77 +590,23 @@ switch(x,
switch(y, 1, 2, 3)
```

## Long function calls

Strive to limit your code to 80 characters per line. This fits comfortably on a
printed page with a reasonably sized font. If you find yourself running out of
room, this is a good indication that you should encapsulate some of the work in
a separate function.
## Semicolons

If a function call is too long to fit on a single line, use one line each for
the function name, each argument, and the closing `)`.
This makes the code easier to read and to change later.
Semicolons are never recommended.
In particular, don't put `;` at the end of a line, and don't use `;` to put multiple commands on one line.

```{r}
# Good
do_something_very_complicated(
something = "that",
requires = many,
arguments = "some of which may be long"
)
my_helper()
my_other_helper()

# Bad
do_something_very_complicated("that", requires, many, arguments,
"some of which may be long"
)
```

As described under [Named arguments](#argument-names), you can omit the argument names
for very common arguments (i.e. for arguments that are used in almost every
invocation of the function). If this introduces a large disparity between the line lengths, you may want to supply names anyway:
my_helper();
my_other_helper();

```{r}
# Good
my_function(
x,
long_argument_name,
extra_argument_a = 10,
extra_argument_b = c(1, 43, 390, 210209)
)

# Also good
my_function(
x = x,
y = long_argument_name,
extra_argument_a = 10,
extra_argument_b = c(1, 43, 390, 210209)
)
{ my_helper(); my_other_helper() }
```

You may place multiple unnamed arguments on the same line if they are closely
related to each other. A common example of this is creating strings
with `paste()`. In such cases, it's often beneficial to match one line of code
to one line of output.

```{r}
# Good
paste0(
"Requirement: ", requires, "\n",
"Result: ", result, "\n"
)

# Bad
paste0(
"Requirement: ", requires,
"\n", "Result: ",
result, "\n")
```

## Semicolons

Don't put `;` at the end of a line, and don't use `;` to put multiple commands
on one line.

## Assignment

Use `<-`, not `=`, for assignment.
Expand Down
Loading