/
conditions.Rmd
240 lines (180 loc) · 7.82 KB
/
conditions.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
---
title: "Conditions"
output:
github_document:
toc: true
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(cache = FALSE)
knitr::opts_chunk$set(echo = TRUE)
knitr::opts_chunk$set(fig.path = "img/")
.libPaths('/packages')
my_packages <- c('rlang')
using<-function(...) {
# https://stackoverflow.com/a/44660688
libs<-unlist(list(...))
req<-unlist(lapply(libs,require,character.only=TRUE))
need<-libs[req==FALSE]
if(length(need)>0){
install.packages(need)
lapply(need,require,character.only=TRUE)
}
}
using(my_packages)
```
# Introduction
Notes from [Advanced R](https://adv-r.hadley.nz/conditions.html).
The **condition** system provides a paired set of tools that allow the author
of a function to indicate that something unusual is happening, and the user of
that function to deal with it. The function author **signals** conditions with
functions like:
* `stop()` for errors,
* `warning()` for warnings, and
* `message()` for messages,
then the function user can handle them with functions like `tryCatch()` and
`withCallingHandlers()`.
Understanding the condition system is important because you'll often need to
play both roles: signalling conditions from the functions you create, and
handle conditions signalled by the functions you call.
R offers a very powerful condition system based on ideas from Common Lisp. Like
R's approach to Object-Oriented Programming, it is rather different to
currently popular programming languages so it is easy to misunderstand.
## Signalling conditions
There are three conditions that you can signal in code: errors, warnings, and
messages.
* Errors are the most severe; they indicate that there is no way for a function
to continue and execution must stop.
* Warnings fall somewhat in between errors and message, and typically indicate
that something has gone wrong but the function has been able to at least
partially recover.
* Messages are the mildest; they are way of informing users that some action
has been performed on their behalf.
There is a final condition that can only be generated interactively: an
interrupt, which indicates that the user has interrupted execution.
Conditions are usually displayed prominently, in a bold font or coloured red,
depending on the R interface. You can tell them apart because errors always
start with "Error", warnings with "Warning" or "Warning message", and messages
with nothing.
### Errors
In base R, errors are signalled, or **thrown**, by `stop()`.
```{r stop, error=TRUE}
f <- function() g()
g <- function() h()
h <- function() stop("This is an error!")
f()
```
By default, the error message includes the call, but this is typically not
useful (and recapitulates information that you can easily get from
`traceback()`), so Hadley recommends using `call. = FALSE`.
```{r stop_call_false, error=TRUE}
h <- function() stop("This is an error!", call. = FALSE)
f()
```
The rlang equivalent to `stop()`, `rlang::abort()`, does this automatically.
```{r abort, error=TRUE}
h <- function() abort("This is an error!")
f()
```
The best error messages tell you what is wrong and point you in the right
direction to fix the problem. Writing good error messages is hard because
errors usually occur when the user has a flawed mental model of the function.
As a developer, it's hard to imagine how the user might be thinking incorrectly
about your function, and thus it's hard to write a message that will steer the
user in the correction direction. See the [tidyverse style
guide](https://style.tidyverse.org/error-messages.html) for some general
principles.
### Warnings
Warnings, signalled by `warning()`, are weaker that errors: they signal that
something has gone wrong, but the code has been able to recover and continue.
Unlike errors, you can have multiple warnings from a single function call.
```{r warning}
fw <- function() {
cat("1\n")
warning("W1")
cat("2\n")
warning("W2")
cat("3\n")
warning("W3")
}
```
By default, warnings are cached and printed only when control returns to the
top level.
```{r call_function_with_warning}
fw()
```
You can control this behaviour with the `warn` option:
* To make warnings appear immediately, set `options(warn = 1)`.
* To turn warnings into errors, set `options(warn = 2)`. This is usually the
easiest way to debug a warning, as once it's an error you can use tools like
`traceback()` to find the source.
* Restore the default behaviour with `options(warn = 0)`.
Like `stop()`, `warning()` also has a call argument. It is slightly more useful
(since warnings are often more distant from their source), but Hadley still
recommends suppressing it with `call. = FALSE`. The equivalent in rlang is
`rlang::warn()` and it suppresses the `call.` by default.
Warnings occupy a somewhat challenging place between messages ("you should know
about this") and errors ("you must fix this!"). In general, **be restrained**,
as warnings are easy to miss if there's a lot of other output, and you don't
want your function to recover too easily from clearly invalid input.
There are only a couple of cases where using a warning is clearly appropriate:
* When you **deprecate** a function you want to allow older code to continue to
work (so ignoring the warning is OK) but you want to encourage the user to
switch to a new function.
* When you are reasonably certain you can recover from a problem: If you were
100% certain that you could fix the problem, you wouldn't need any message;
if you were more uncertain that you could correctly fix the issue, you'd
throw an error.
### Messages
Messages, signalled by `message()`, are informational; use them to tell the
user that you've done something on their behalf. Good messages are a balancing
act: you want to provide just enough information so the user knows what's going
on, but not so much that they're overwhelmed.
`message()` are displayed immediately and do not have a `call.` argument.
```{r message}
fm <- function() {
cat("1\n")
message("M1")
cat("2\n")
message("M2")
cat("3\n")
message("M3")
}
fm()
```
Good places to use a message are:
* When a default argument requires some non-trivial amount of computation and
you want to tell the user what value was used. For example, ggplot2 reports
the number of bins used if you don't supply a `binwidth`.
* In functions that are called primarily for their side-effects which would
otherwise be silent. For example, when writing files to disk, calling a web
API, or writing to a database, it's useful to provide regular status messages
telling the user what's going on.
* When you are about to start a long running process with no intermediate
output. A progress bar is better but a message is a good place to start.
* When writing a package, you sometimes want to display a message when your
package is loaded (i.e. in `.onAttach()`); here you must use
`packageStartupMessage()`.
Generally any function that produces a message should have some way to suppress
it, like a `quiet = TRUE` argument. It is possible to suppress all messages
with `suppressMessages()` but it is nice to provide finer grained control.
The `cat()` function is closely related and it terms of usage and result, they
appear quite similar.
```{r cat_vs_message}
cat("Hi!\n")
message("Hi!")
```
However, the _purposes_ of `cat()` and `message()` are different. Use `cat()`
**when the primary role of the function is to print to the console**, like
`print()` or `str()` methods. Use `message()` as a **side-channel to print to
the console when the primary purpose of the function is something else**. In
other words, `cat()` is for when the user _asks_ for something to be printed
and `message()` is for when the developer _elects_ to print something.
## Session info
This document was generated by rendering `oop.Rmd` using `rmd_to_md.sh`.
```{r time, echo=FALSE}
Sys.time()
```
Session info.
```{r session_info, echo=FALSE}
sessionInfo()
```