generated from r4ds/bookclub-template
-
Notifications
You must be signed in to change notification settings - Fork 25
/
08_Conditions.Rmd
549 lines (406 loc) · 12.3 KB
/
08_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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
# Conditions
**Learning objectives:**
- What conditions are
- How to use them
## Introduction
What are conditions? Problems that happen in functions:
- Error
- Warning
- Message
As a function author, one can signal them--that is, say there's a problem.
As a function consumer, one can handle them--for example, react or ignore.
## Signalling conditions
### Types of conditions
Three types of conditions:
- `r emoji::emoji("x")` **Errors.** Problem arose, and the function cannot continue.
- `r emoji::emoji("warning")` **Warnings.** Problem arose, but the function can continue, if only partially.
- `r emoji::emoji("speech_balloon")` **Messages.** Something happened, and the user should know.
### `r emoji::emoji("x")` Errors
How to throw errors
```{r throwing_errors}
# with base R
stop("... in the name of love...")
# with rlang
rlang::abort("...before you break my heart...")
# with base R; without call
stop("... think it o-o-over...", call. = FALSE)
```
Composing error messages
- Mechanics.
- `stop()` pastes together arguments
```{r}
some_val <- 1
stop("Your value is: ", some_val, call. = FALSE)
```
- `abort()` requires `{glue}`
```{r}
some_val <- 1
rlang::abort(glue::glue("Your value is: {some_val}"))
```
- Style. See [here](http://style.tidyverse.org/error-messages.html).
### `r emoji::emoji("warning")` Warnings
May have multiple warnings per call
```{r}
warn <- function() {
warning("This is your first warning")
warning("This is your second warning")
warning("This is your LAST warning")
}
```
Print all warnings once call is complete.
```{r}
warn()
```
Like errors, `warning()` has
- a call argument
- an `{rlang}` analog
```{r}
# base R
# ... with call (implicitly .call = TRUE)
warning("Warning")
# ... with call suppressed
warning("Warning", call. = FALSE)
# rlang
# note: call suppressed by default
rlang::warn("Warning")
```
(Hadley's) advice on usage:
- Err on the side of errors. In other words, error rather than warn.
- But warnings make sense in a few cases:
- Function is being deprecated. Warn that it is reaching end of life.
- Function is reasonably sure to recover from issue.
### `r emoji::emoji("speech_balloon")` Messages
Mechanics:
- Issued immediately
- Do not have a call argument
Style:
Messages are best when they inform about:
- Default arguments
- Status updates of for functions used primarily for side-effects (e.g., interaction with web API, file downloaded, etc.)
- Progress of long-running process (in the absence of a status bar).
- Package loading message (e.g., attaching package, objects masked)
## Ignoring conditions
A few ways:
- `try()`
- `suppressWarnings()`
- `suppressMessages()`
### `try()`
What it does:
- Displays error
- But continues execution after error
```{r}
bad_log <- function(x) {
try(log(x))
10
}
bad_log("bad")
```
Better ways to react to/recover from errors:
1. Use `tryCatch()` to "catch" the error and perform a different action in the event of an error.
1. Set a default value inside the call. See below.
```{r}
default <- NULL
try(default <- read.csv("possibly-bad-input.csv"), silent = TRUE)
```
### `suppressWarnings()`, `suppressMessages()`
What it does:
- Supresses all warnings (messages)
```{r}
# suppress warnings (from our `warn()` function above)
suppressWarnings(warn())
# suppress messages
many_messages <- function() {
message("Message 1")
message("Message 2")
message("Message 3")
}
suppressMessages(many_messages())
```
## Handling conditions
Every condition has a default behavior:
- `r emoji::emoji("x")` Errors halt execution
- `r emoji::emoji("warning")` Warnings are collected during execution and displayed in bulk after execution
- `r emoji::emoji("speech_balloon")` Messages are displayed immediately
Condition handlers allow one to change that behavior (within the scope of a function).
Two handler functions:
- `tryCatch()`
- `withCallingHandlers()`
```{r, eval=FALSE}
# try to run `code_to_try_to_run`
# if (error) condition is signalled, fun some other code
tryCatch(
error = function(cnd) {
# code to run when error is thrown
},
code_to_try_to_run
)
# try to `code_to_try_to_run`
# if condition is signalled, run code corresponding to condition type
withCallingHandlers(
warning = function(cnd) {
# code to run when warning is signalled
},
message = function(cnd) {
# code to run when message is signalled
},
code_to_try_to_run
)
```
### Condition objects
```{r}
# catch a condition
cnd <- rlang::catch_cnd(stop("An error"))
# inspect it
str(cnd)
```
The standard components
- `message`. The error message. To extract it, use `conditionMessage(cnd)`.
- `call`. The function call that triggered the condition. To extract it, use `conditionCall(cnd)`.
But custom conditions may contain other components.
### Exiting handlers
If a condition is signalled, this type of handler controls what code to run before exiting the function call.
```{r}
f3 <- function(x) {
tryCatch(
# if error signalled, return NA
error = function(cnd) NA,
# try to run log
log(x)
)
}
f3("x")
```
When a condition is signalled, control moves to the handler and never returns to the original code.
```{r}
tryCatch(
message = function(cnd) "There",
{
message("Here")
stop("This code is never run!")
}
)
```
The `tryCatch()` exit handler has one final argument: `finally`. This is run regardless of the condition of the original code. This is often used for clean-up.
```{r}
# try to write text to disk
# if an error is signalled--for example, `path` does not exist
# or if no condition is signalled
# that is in both cases, the code block in `finally` is executed
path <- tempfile()
tryCatch(
{
writeLines("Hi!", path)
# ...
},
finally = {
# always run
unlink(path)
}
)
```
### Calling handlers
Definition by verbal comparison:
- With exit handlers, code exits the normal flow once a condition is signalled
- With calling handlers, code continues in the normal flow once control is returned by the handler.
Definition by code comparison:
```{r}
# with an exit handler, control moves to the handler once condition signalled and does not move back
tryCatch(
message = function(cnd) cat("Caught a message!\n"),
{
message("Someone there?")
message("Why, yes!")
}
)
# with a calling handler, control moves first to the handler and the moves back to the main code
withCallingHandlers(
message = function(cnd) cat("Caught a message!\n"),
{
message("Someone there?")
message("Why, yes!")
}
)
```
### By default, conditions propagate
Let's suppose that there are nested handlers. If a condition is signalled in the child, it propagates to its parent handler(s).
```{r}
# Bubbles all the way up to default handler which generates the message
withCallingHandlers(
message = function(cnd) cat("Level 2\n"),
withCallingHandlers(
message = function(cnd) cat("Level 1\n"),
message("Hello")
)
)
# Bubbles up to tryCatch
tryCatch(
message = function(cnd) cat("Level 2\n"),
withCallingHandlers(
message = function(cnd) cat("Level 1\n"),
message("Hello")
)
)
```
### But conditions can be muffled
If one wants to "muffle" the siginal, one needs to use `rlang::cnd_muffle()`
```{r}
# Muffles the default handler which prints the messages
withCallingHandlers(
message = function(cnd) {
cat("Level 2\n")
rlang::cnd_muffle(cnd)
},
withCallingHandlers(
message = function(cnd) cat("Level 1\n"),
message("Hello")
)
)
# Muffles level 2 handler and the default handler
withCallingHandlers(
message = function(cnd) cat("Level 2\n"),
withCallingHandlers(
message = function(cnd) {
cat("Level 1\n")
rlang::cnd_muffle(cnd)
},
message("Hello")
)
)
```
### Call stacks
Call stacks of exiting and calling handlers differ.
Why?
> Calling handlers are called in the context of the call that signalled the condition
> exiting handlers are called in the context of the call to tryCatch()
To see this, consider how the call stacks differ for a toy example.
```{r}
# create a function
f <- function() g()
g <- function() h()
h <- function() message
# call stack of calling handlers
withCallingHandlers(f(), message = function(cnd) {
lobstr::cst()
rlang::cnd_muffle(cnd)
})
# call stack of exit handlers
tryCatch(f(), message = function(cnd) lobstr::cst())
tryCatch(f(), message = function(cnd) lobstr::cst())
```
## Custom conditions
### Motivation
The `base::log()` function provides a minimal error message.
```{r}
log(letters)
log(1:10, base = letters)
```
One could make a more informative error message about which argument is problematic.
```{r}
my_log <- function(x, base = exp(1)) {
if (!is.numeric(x)) {
rlang::abort(paste0(
"`x` must be a numeric vector; not ", typeof(x), "."
))
}
if (!is.numeric(base)) {
rlang::abort(paste0(
"`base` must be a numeric vector; not ", typeof(base), "."
))
}
base::log(x, base = base)
}
```
Consider the difference:
```{r}
my_log(letters)
my_log(1:10, base = letters)
```
### Signalling
Create a helper function to describe errors:
```{r}
abort_bad_argument <- function(arg, must, not = NULL) {
msg <- glue::glue("`{arg}` must {must}")
if (!is.null(not)) {
not <- typeof(not)
msg <- glue::glue("{msg}; not {not}.")
}
rlang::abort(
"error_bad_argument", # <- this is the (error) class, I believe
message = msg,
arg = arg,
must = must,
not = not
)
}
```
Rewrite the log function to use this helper function:
```{r}
my_log <- function(x, base = exp(1)) {
if (!is.numeric(x)) {
abort_bad_argument("x", must = "be numeric", not = x)
}
if (!is.numeric(base)) {
abort_bad_argument("base", must = "be numeric", not = base)
}
base::log(x, base = base)
}
```
See the result for the end user:
```{r}
my_log(letters)
my_log(1:10, base = letters)
```
### Handling
Use class of condition object to allow for different handling of different types of errors
```{r}
tryCatch(
error_bad_argument = function(cnd) "bad_argument",
error = function(cnd) "other error",
my_log("a")
)
```
But note that the first handler that matches any of the signal's class, potentially in a vector of signal classes, will get control. So put the most specific handlers first.
## Applications
See [the sub-section in the book](https://adv-r.hadley.nz/conditions.html#condition-applications) for excellent examples.
## Resources
- Conditions articles in rlang vignettes:
- [Including function calls in error messages](https://rlang.r-lib.org/reference/topic-error-call.html)
- [Including contextual information with error chains](https://rlang.r-lib.org/reference/topic-error-chaining.html)
- [Formatting messages with cli](https://rlang.r-lib.org/reference/topic-condition-formatting.html)
- [Other resources](https://github.com/rstudio-conf-2022/pkg-dev-masterclass/blob/main/materials/5-error-resources.md) from error message segment of rstudio::conf(2022) workshop "Package Development Masterclass"
## Meeting Videos
### Cohort 1
`r knitr::include_url("https://www.youtube.com/embed/mwiNe083DLU")`
### Cohort 2
`r knitr::include_url("https://www.youtube.com/embed/ZFUr7YRSu2o")`
### Cohort 3
`r knitr::include_url("https://www.youtube.com/embed/UZhrsVz6wi0")`
`r knitr::include_url("https://www.youtube.com/embed/Wt7p71_BuYY")`
### Cohort 4
`r knitr::include_url("https://www.youtube.com/embed/WinIo5mrUZo")`
### Cohort 5
`r knitr::include_url("https://www.youtube.com/embed/VFs-2sl5C70")`
### Cohort 6
`r knitr::include_url("https://www.youtube.com/embed/VwmrbPUQY1k")`
<details>
<summary> Meeting chat log </summary>
```
00:19:16 Trevin: https://style.tidyverse.org/error-messages.html
00:20:14 Trevin: More on errors in the design guide: https://design.tidyverse.org/
01:14:27 Federica Gazzelloni: more info here: https://colinfay.me/learn-shiny-production/
```
</details>
### Cohort 7
`r knitr::include_url("https://www.youtube.com/embed/t1N6XdidvNo")`
<details>
<summary>Meeting chat log</summary>
```
00:34:09 Ron: Someone did: https://cran.r-project.org/web/packages/comprehenr/vignettes/Introduction.html
00:47:58 collinberke: https://purrr.tidyverse.org/reference/safely.html
00:48:24 Ron: it's a function operator !
00:49:37 Ron: \(x) length(unique(x) is not too verbose though
00:49:39 Ron: ;)
01:06:50 collinberke: https://colinfay.me/purrr-mappers/
01:07:45 collinberke: https://colinfay.me/purrr-web-mining/
```
</details>