-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME.Rmd
244 lines (182 loc) · 7.44 KB
/
README.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
---
output: github_document
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, echo = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "README-"
)
```
[![Build Status](https://travis-ci.org/egnha/rong.svg?branch=master)](https://travis-ci.org/egnha/rong)
[![codecov](https://codecov.io/gh/egnha/rong/branch/master/graph/badge.svg)](https://codecov.io/gh/egnha/rong)
# rong
_The rong approach to wrong inputs. Two (w)rongs make a right._
> _rong_ is a complete reimplementation of
> [valaddin](https://github.com/egnha/valaddin) that supports tidyverse
> semantics.
>
> Since this is a major break in the API of the current
> [CRAN version](https://cran.r-project.org/package=valaddin) of valaddin (which
> appears to have no reverse dependencies), it make sense to start from a clean
> slate and distinguish this reconception with a new name.
## Overview
Dealing with invalid function inputs is a chronic pain for R users, given R’s
weakly typed nature. _rong_ provides pain relief in the form of an adverb,
`firmly()`, that enables you to _transform_ an existing function into a function
with input validation checks, in a manner suitable for both programmatic and
interactive use.
Additionally, rong provides:
* `fasten()`, to help you write cleaner and more explicit function
declarations in your scripts, by providing a _functional operator_ that
“fastens” a given set of input validations to functions (i.e., it
[curries](https://en.wikipedia.org/wiki/Currying) `firmly()`)
* `validate()`, as syntactic sugar to validate _objects_, by applying input
validation to the identity function
* `loosely()`, to undo the application of input validation checks, at any
time, by returning the original function
These functions support
[tidyverse semantics](https://rpubs.com/hadley/dplyr-programming) such as
[quasiquotation](http://rlang.tidyverse.org/reference/quasiquotation.html) and
[splicing](http://rlang.tidyverse.org/reference/quasiquotation.html), to provide
a flexible yet simple grammar for input validations.
## Installation
```{r, eval = FALSE}
# install.packages("devtools")
devtools::install_github("egnha/rong")
```
## Usage
To illustrate rong’s functional approach to input validation, consider the
function that computes the barycentric coordinates of a point in the plane:
```{r}
bc <- function(x, y) {
c(x, y, 1 - x - y)
}
```
### When validating inputs, think _function transformation_
Imagine applying `bc()` “firmly,” exactly as before, but with the assurance that
the inputs are indeed numeric. To enable this, transform `bc()` using
`firmly()`, relative to the validation specified by the predicate function
`is.numeric()`:
```{r error = TRUE, purl = FALSE}
library(rong)
bc2 <- firmly(bc, is.numeric)
bc2(.5, .2)
bc2(.5, ".2")
```
### Specify error messages that are context-aware
Using the string-interpolation syntax provided by the
[glue](https://github.com/tidyverse/glue) package, make error messages more
informative, by taking into account the context of an error:
```{r error = TRUE, purl = FALSE}
bc3 <- firmly(bc, "{{.}} is not numeric (type: {typeof(.)})" := is.numeric)
bc3(.5i, ".2")
```
### Express input validations using tidyverse idioms
rong supports
[quasiquotation](http://rlang.tidyverse.org/reference/quasiquotation.html) and
[splicing](http://rlang.tidyverse.org/reference/quasiquotation.html) semantics
for specifying input validation checks. Checks and (custom) error messages are
captured as [quosures](http://rlang.tidyverse.org/reference/quosure.html), to
ensure that validations, and their error reports, are hygienically evaluated in
the intended scope—transparently to the user.
```{r error = TRUE, purl = FALSE}
z <- 0
in_triangle <- vld_spec(
"{{.}} is not positive (value is {.})" :=
{isTRUE(. > !! z)}(x, y, 1 - x - y)
)
bc4 <- firmly(bc, is.numeric, !!! in_triangle)
bc4(.5, .2)
bc4(.5, .6)
```
This reads as follows:
* `vld_spec()` encapsulates the condition that `x`, `y`, `1 - x - y` are
positive, as a formula
[definition](http://rlang.tidyverse.org/reference/quosures.html#details).
The predicate itself is succinctly expressed using
[magrittr](https://github.com/tidyverse/magrittr)’s shorthand for anonymous
functions. The unquoting operator `!!` ensures that the _value_ of `z` is
“burned into” the check.
* The additional condition that `(x, y)` lies in a triangle is imposed by
splicing it in with the `!!!` operator.
### Use the same grammar to validate objects
Validating an object (say, a data frame) is nothing other than applying an
input-validated identity function to it. The function `validate()` provides a
shorthand for this.
```{r error = TRUE, purl = FALSE}
# All assumptions OK, mtcars returned invisibly
validate(mtcars,
is.data.frame,
chk_lt(100, nrow(.)),
chk_has_names(c("mpg", "cyl")))
validate(mtcars,
is.data.frame,
chk_gt(100, nrow(.)),
chk_has_name("cylinders"))
```
### Clarify code structure
Instead of writing
```{r}
bc_cluttered <- function(x, y) {
if (!is.numeric(x) || length(x) != 1)
stop("x is not a number")
if (!is.numeric(y) || length(y) != 1)
stop("y is not a number")
if (!isTRUE(x > 0))
stop("x is not positive")
if (!isTRUE(y > 0))
stop("y is not in the upper-half plane")
if (!isTRUE(1 - x - y > 0))
stop("1 - x - y is not positive")
c(x, y, 1 - x - y)
}
```
use `fasten()` to highlight the core logic, while keeping input assumptions in
sight:
```{r error = TRUE, purl = FALSE}
bc_clean <- fasten(
"{{.}} is not a number" := {is.numeric(.) && length(.) == 1},
"{{.}} is not positive" :=
{isTRUE(. > 0)}(x, "y is not in the upper-half plane" := y, 1 - x - y)
)(
function(x, y) {
c(x, y, 1 - x - y)
}
)
bc_clean(.5, .2)
bc_clean(c(.5, .5), -.2)
```
In addition to having cleaner code, you can:
* reduce duplication, by using the splicing operator `!!!` to
reuse common input validations
* recover the underlying “lean” function, at any time, using `loosely()`:
```{r}
loosely(bc_clean)
```
## Related packages
* rong provides a basic set of predicate functions—prefixed `chk_` for
easy lookup—to specify common kinds of checks, e.g., type and property
checks, comparisons, etc.
To enrich rong’s vocabulary of predicate functions, use:
* specialized collections of predicate functions, such as
[assertive](https://bitbucket.org/richierocks/assertive),
[assertthat](https://github.com/hadley/assertthat),
[checkmate](https://github.com/mllg/checkmate)
* [vetr](https://github.com/brodieG/vetr), which provides a concise
declarative syntax to create custom predicate functions
* Other non-functional approaches to input validation:
[argufy](https://github.com/gaborcsardi/argufy),
[ensurer](https://github.com/smbache/ensurer),
[typeCheck](https://github.com/jimhester/typeCheck)
## Acknowledgement
rong makes essential use of the [rlang](https://github.com/tidyverse/rlang)
package by [Lionel Henry](https://github.com/lionel-) and
[Hadley Wickham](https://github.com/hadley), which provides the engine for
quasiquotation and expression capture. The
[glue](https://github.com/tidyverse/glue) package by
[Jim Hester](https://github.com/jimhester) enables string interpolation of error
messages.
## License
MIT Copyright © 2017 [Eugene Ha](https://github.com/egnha)