firmly: Apply a function firmly

Description Usage Arguments How to specify validation checks How to specify error messages See Also Examples

Description

The main functions of rong apply or undo input validation checks to functions.

These are supplemented by:

Usage

1
2
3
4
5
6
7
8
9
fasten(..., error_class = NULL)

firmly(f, ..., error_class = NULL)

loosely(f)

vld_error_cls(f)

is_firm(x)

Arguments

f

Function.

...

Input validation checks (with support for quasiquotation).

error_class

Subclass of the error condition to be raised when an input validation error occurs (character). If NULL (the default), the error subclass is inputValidationError.

x

Object to test.

How to specify validation checks

A validation check is specified by a predicate function: if the predicate yields TRUE, the check passes, otherwise the check fails. Any predicate function will do, provided its first argument is the object to be checked.

Apply a validation check to all arguments

Simply write the predicate when you want to apply it to all (named) arguments.

Example — To transform the function

1
    add <- function(x, y, z) x + y + z

so that every argument is checked to be numeric, use the predicate is.numeric():

1
2
3
    add_num <- firmly(add, is.numeric)
    add_num(1, 2, 3)        # 6
    add_num(1, 2, "three")  # Error: 'FALSE: is.numeric(z)'

Restrict a validation check to specific expressions

Specifiy expressions (of arguments) when you want to restrict the scope of a check.

Example — To require that y and z are numeric (but not x necessarily), specify them as arguments of is.numeric() (this is valid even though is.numeric(), as a function, only takes a single argument):

1
2
3
    add_num_yz <- firmly(add, is.numeric(y, z))
    add_num_yz(TRUE, 2, 3)     # 6
    add_num_yz(TRUE, TRUE, 3)  # Error: 'FALSE: is.numeric(y)'

Set predicate parameters

If a predicate has (named) parameters, you can set them as part of the check. The format for setting the parameters of a predicate, as a validation check, is

1
    predicate(<params_wo_default_value>, ..., <params_w_default_value>)

where ... is filled by the expressions to check, which you may omit when you intend to check all arguments. The order of predicate arguments is preserved within the two groups (parameters without default value vs those with default value).

Thus the rule for setting the parameters of predicate() as a validation check is the same as that of predicate() as a function.

Example — You can match a regular expression with the following wrapper around grepl():

1
2
3
    matches_regex <- function(x, regex, case_sensitive = TRUE) {
      isTRUE(grepl(regex, x, ignore.case = !case_sensitive))
    }

As a validation check, the format for setting the parameters of this predicate is

1
    matches_regex(regex, ..., case_sensitive = TRUE)

Thus the value of regex must be set, and may be matched by position. Setting case_sensitive is optional, and must be done by name.

1
2
3
4
5
6
    scot <- function(me, you) {
      paste0("A'm ", me, ", whaur ye fae, ", you, "?")
    }
    scot <- firmly(scot, matches_regex("^mc.*$", case_sensitive = FALSE, me))
    scot("McDonald", "George")  # "A'm McDonald, whaur ye fae, George?"
    scot("o'neill", "George")   # Error

Succinctly express short predicates

Short predicates of a single argument can be succinct expressed by their body alone (enclosed in curly braces). Use . (dot) to indicate the argument.

Example — Monotonicity of arguments can be expressed using an ordinary (anonymous) function declaration

1
2
3
    add_inc <- firmly(add, (function(.) isTRUE(. > 0))(y - x, z - y))
    add_inc(1, 2, 3)  # 6
    add_inc(1, 2, 2)  # Error: 'FALSE: (function(.) isTRUE(. > 0))(z - y)'

or more succinctly like so

1
2
3
    add_inc <- firmly(add, {isTRUE(. > 0)}(y - x, z - y))
    add_inc(1, 2, 3)  # 6
    add_inc(1, 2, 2)  # Error: 'FALSE: (function (.) {isTRUE(. > 0)})(z - y)'

How to specify error messages

You don't have to specify them at all—they are automatically generated by default, and are typically informative enough to enable you to identify the cause of failure. Nonetheless, you can make errors more comprehensible, and poinpoint their source more quickly, by providing additional contextual information.

Generally, error messages for validation checks are set by attaching them to the predicate. The syntax is

1
    <error_message> := predicate

In the simplest case, <error_message> is just a literal string, such as "x is not positive". But it can also be a “smart string,” which can encode context-specific information.

Specify the error message of a predicate

TODO

Specify an error message for a specific expression

TODO

Context-aware string interpolation of error messages

TODO: - scope/context of string interpolation (meaning of {{.}}) - Two interpretations of dot (is this best?) - use of pronouns .expr, .value

See Also

vld_spec(), vld_exprs(), validate, predicates, new_vld_error_msg()

Examples

 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
bc <- function(x, y) c(x, y, 1 - x - y)

## Ensure that inputs are numeric
bc1 <- firmly(bc, is.numeric)
bc1(.5, .2)
## Not run: 
bc1(.5, ".2")
## End(Not run)

## Use custom error messages
bc2 <- firmly(bc, "{{.}} is not numeric (type: {typeof(.)})" := is.numeric)
## Not run: 
bc2(.5i, ".2")
## End(Not run)

## Fix values using Tidyverse quasiquotation
z <- 0
in_triangle <- vld_spec(
  "{{.}} is not positive (value is {.})" :=
    {isTRUE(. > !! z)}(x, y, 1 - x - y)
)
bc3 <- firmly(bc, is.numeric, !!! in_triangle)
bc3(.5, .2)
## Not run: 
bc3(.5, .6)
## End(Not run)

## Highlight the core logic with fasten()
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)
  }
)

## Recover the underlying function with loosely()
loosely(bc_clean)

egnha/rong documentation built on May 7, 2019, 9:48 p.m.