Coder Social home page Coder Social logo

valaddin's Introduction

Development has moved to rong

valaddin

R-CMD-check CRAN_Status_Badge stability-frozen

Dealing with invalid function inputs is a chronic pain for R users, given R’s weakly typed nature. valaddin provides pain relief—a lightweight R package that enables you to transform an existing function into a function with input validation checks, in situ, in a manner suitable for both programmatic use and interactive sessions.

Installation

Install from CRAN

install.packages("valaddin")

or get the development version from GitHub using the devtools package

# install.packages("devtools")
devtools::install_github("egnha/valaddin", ref = "dev", build_vignettes = TRUE)

Why use valaddin

Fail fast—save time, spare confusion

You can be more confident your function works correctly, when you know its arguments are well-behaved. But when they aren’t, its better to stop immediately and bring them into line, than to let them pass and wreak havoc, exposing yourself to breakages or, worse, silently incorrect results. Validating the inputs of your functions is good defensive programming practice.

Suppose you have a function secant()

secant <- function(f, x, dx) (f(x + dx) - f(x)) / dx

and you want to ensure that the user (or some code) supplies numerical inputs for x and dx. Typically, you’d rewrite secant() so that it stops if this condition is violated:

secant_numeric <- function(f, x, dx) {
  stopifnot(is.numeric(x), is.numeric(dx))
  secant(f, x, dx)
}

secant_numeric(log, 1, .1)
#> [1] 0.9531018

secant_numeric(log, "1", ".1")
#> Error in secant_numeric(log, "1", ".1"): is.numeric(x) is not TRUE

The standard approach in R is problematic

While this works, it’s not ideal, even in this simple situation, because

  • it’s inconvenient for interactive use at the console: you have to declare a new function, and give it a new name (or copy-paste the original function body)

  • it doesn’t catch all errors, only the first that occurs among the checks

  • you’re back to square one, if you later realize you need additional checks, or want to skip them altogether.

valaddin rectifies these shortcomings

valaddin provides a function firmly() that takes care of input validation by transforming the existing function, instead of forcing you to write a new one. It also helps you by reporting every failing check.

library(valaddin)

# Check that `x` and `dx` are numeric
secant <- firmly(secant, list(~x, ~dx) ~ is.numeric)

secant(log, 1, .1)
#> [1] 0.9531018

secant(log, "1", ".1")
#> Error: secant(f = log, x = "1", dx = ".1")
#> 1) FALSE: is.numeric(x)
#> 2) FALSE: is.numeric(dx)

To add additional checks, just apply the same procedure again:

secant <- firmly(secant, list(~x, ~dx) ~ {length(.) == 1L})

secant(log, "1", c(.1, .01))
#> Error: secant(f = log, x = "1", dx = c(0.1, 0.01))
#> 1) FALSE: is.numeric(x)
#> 2) FALSE: (function(.) {length(.) == 1L})(dx)

Or, alternatively, all in one go:

secant <- loosely(secant)  # Retrieves the original function
secant <- firmly(secant, list(~x, ~dx) ~ {is.numeric(.) && length(.) == 1L})

secant(log, 1, .1)
#> [1] 0.9531018

secant(log, "1", c(.1, .01))
#> Error: secant(f = log, x = "1", dx = c(0.1, 0.01))
#> 1) FALSE: (function(.) {is.numeric(.) && length(.) == 1L})(x)
#> 2) FALSE: (function(.) {is.numeric(.) && length(.) == 1L})(dx)

Check anything using a simple, consistent syntax

firmly() uses a simple formula syntax to specify arbitrary checks—not just type checks. Every check is a formula of the form <where to check> ~ <what to check>. The “what” part on the right is a function that does a check, while the (form of the) “where” part on the left indicates where to apply the check—at which arguments or expressions thereof.

valaddin provides a number of conveniences to make checks for firmly() informative and easy to specify.

Use custom error messages

Use a custom error message to clarify the purpose of a check:

bc <- function(x, y) c(x, y, 1 - x - y)

# Check that `y` is positive
bc_uhp <- firmly(bc, list("(x, y) not in upper half-plane" ~ y) ~ {. > 0})

bc_uhp(.5, .2)
#> [1] 0.5 0.2 0.3

bc_uhp(.5, -.2)
#> Error: bc_uhp(x = 0.5, y = -0.2)
#> (x, y) not in upper half-plane

Easily apply a check to all arguments

Leave the left-hand side of a check formula blank to apply it to all arguments:

bc_num <- firmly(bc, ~is.numeric)

bc_num(.5, ".2")
#> Error: bc_num(x = 0.5, y = ".2")
#> FALSE: is.numeric(y)

bc_num(".5", ".2")
#> Error: bc_num(x = ".5", y = ".2")
#> 1) FALSE: is.numeric(x)
#> 2) FALSE: is.numeric(y)

Or fill in a custom error message:

bc_num <- firmly(bc, "Not numeric" ~ is.numeric)

bc_num(.5, ".2")
#> Error: bc_num(x = 0.5, y = ".2")
#> Not numeric: `y`

Check conditions with multi-argument dependencies

Use the isTRUE() predicate to implement checks depending on multiple arguments or, equivalently, the check maker vld_true():

in_triangle <- function(x, y) {x >= 0 && y >= 0 && 1 - x - y >= 0}
outside <- "(x, y) not in triangle"

bc_tri <- firmly(bc, list(outside ~ in_triangle(x, y)) ~ isTRUE)

# Or more concisely:
bc_tri <- firmly(bc, vld_true(outside ~ in_triangle(x, y)))

# Or more concisely still, by relying on an auto-generated error message:
# bc_tri <- firmly(bc, vld_true(~in_triangle(x, y)))

bc_tri(.5, .2)
#> [1] 0.5 0.2 0.3

bc_tri(.5, .6)
#> Error: bc_tri(x = 0.5, y = 0.6)
#> (x, y) not in triangle

Make your code more intelligible

To make your functions more intelligible, declare your input assumptions and move the core logic to the fore. You can do this using firmly(), in several ways:

  • Precede the function header with input checks, by explicitly assigning the function to firmly()’s .f argument:

    bc <- firmly(
      ~is.numeric,
      ~{length(.) == 1L},
      vld_true(outside ~ in_triangle(x, y)),
      .f = function(x, y) {
        c(x, y, 1 - x - y)
      }
    )
    
    bc(.5, .2)
    #> [1] 0.5 0.2 0.3
    
    bc(.5, c(.2, .1))
    #> Error: bc(x = 0.5, y = c(0.2, 0.1))
    #> 1) FALSE: (function(.) {length(.) == 1L})(y)
    #> 2) Error evaluating check (function (x) is.logical(x) && length(x) == 1L && !is.na(x) && x)(in_triangle(x, y)): 'length = 2' in coercion to 'logical(1)'
    
    bc(".5", 1)
    #> Error: bc(x = ".5", y = 1)
    #> 1) FALSE: is.numeric(x)
    #> 2) (x, y) not in triangle
  • Use the magrittr %>% operator to deliver input checks, by capturing them as a list with firmly()’s .checklist argument:

    library(magrittr)
    
    bc2 <- list(
      ~is.numeric,
      ~{length(.) == 1L},
      vld_true(outside ~ in_triangle(x, y))
    ) %>%
      firmly(function(x, y) {
        c(x, y, 1 - x - y)
      },
      .checklist = .)
  • Better yet, use the %checkin% operator:

    bc3 <- list(
      ~is.numeric,
      ~{length(.) == 1L},
      vld_true(outside ~ in_triangle(x, y))
    ) %checkin%
      function(x, y) {
        c(x, y, 1 - x - y)
      }

Learn more

See the package documentation ?firmly, help(p = valaddin) for detailed information about firmly() and its companion functions, and the vignette for an overview of use cases.

Related packages

  • assertive, assertthat, and checkmate provide handy collections of predicate functions that you can use in conjunction with firmly().

  • argufy takes a different approach to input validation, using roxygen comments to specify checks.

  • ensurer and assertr provide a means of validating function values. Additionally, ensurer provides an experimental replacement for function() that builds functions with type-validated arguments.

  • typeCheck, together with Types for R, enables the creation of functions with type-validated arguments by means of special type annotations. This approach is orthogonal to that of valaddin: whereas valaddin specifies input checks as predicate functions with scope, typeCheck specifies input checks as arguments with type.

License

MIT Copyright © 2016–2023 Eugene Ha

valaddin's People

Contributors

egnha avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

valaddin's Issues

Simplify the semantics of addings checks via magrittr pipe

Cf. #19

To keep checks and function header in close proximity, you can use magrittr's %>% operator:

f <- list(
  ~is.numeric,
  vld_true(~length(x) == length(y)
) %>%
  firmly(
    .f = function(x, y) x + y,
    .checklist = .
  )

By permuting the argument signature of firmly from

function(.f, ..., .checklist = list(), .warn_missing = character())

to

function(.checklist = list(), .f, ..., .warn_missing = character())

it'd be possible to slightly simplify the above to

f <- list(
  ~is.numeric,
  vld_true(~length(x) == length(y)
) %>%
  firmly(function(x, y) x + y)

eliminating the need to explicitly specify .f = and .checklist = ..

This is a straightforward fix (albeit somewhat tedious to implement, since many tests will need to be edited to accommodate the new call signature).

Allow .error_class argument to be class or error condition

This enhancement concerns the .error_class argument of firmly(). It would enable the creation of more nuanced error condition objects that could be used to:

  • Embed validation-failure messages, individually, for further handling down the call stack (#31)
  • Modify the call expression via a functional parameter
  • Enable more informative call expression for validate() (#36)

Accordingly, rename .error_class to .error, and set its default value to NULL (which would mean using "simpleError" as condition subclass and the current call-expression transformation (#33)).

Support dot-splicing in firmly

Use dot-splicing (rlang::dots_splice()) to simplify the cumbersome argument signature

function(f, ..., checklist = list(), error_class = character())

to

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

(That is, allow lists of checks to be automatically spliced when included in ....)

Add local checkers for TRUE/FALSE

Name these vld_true, vld_false. They are the most general local checkers possible.

Example: vld_true(~x) should generate the check formula list("Not TRUE: x" ~ x) ~ isTRUE.

strictly() cannot be applied to a function that has an argument named _chks__

This restriction exists because input checks are evaluated in an environment in which _chks__ is forced, thereby overriding a value promised by a (would-be) argument of the same name.

In practice, a function won't have an argument named _chks__ (note the trailing double _), so this restriction only concerns a rare corner case. Nonetheless, this is a stain on strictly(), albeit virtually invisible.

An acceptable removal of this restriction must:

  1. Be localized to changes in the (internal) function validating_closure()
  2. Not degrade performance of evaluating checks

(This is possible using meta-programming, but I want to avoid a hack that would only address an unlikely occurrence.)

loosely() should be as fast as firm_core()

The overhead of calling loosely() should be as low as possible (on the order of calling firm_core()), since a typical use case of it is to recover performance by skipping input validation checks.

Don't use pipe internally

After #29, the only dependencies will be rlang and glue, so the pipe won't be available for internal use. (Eliminating the pipe will also obviate the hack of adding . to the package namespace in order to prevent R CMD check from raising a spurious warning about an “unbound name.”)

Vignette "Using valaddin"

To cover conventions, and example use cases, according to the slogans:

  • Surprises — the downside of dynamic typing
  • When you don't want a handout
  • Don't shoot yourself in the foot
  • Health risks of a lazy evaluation-style

freely()

An alias for nonstrictly(), which produces more readable code, sometimes.

Admit custom condition for input validation errors

If an input validation error occurs, firmly signals a simpleError. For more discriminating error handling downstream, firmly should be allowed to signal other classes of errors. In other words, firmly should have a character-vector parameter .error_class that gives the subclass of the condition, inheriting from c("error", "condition") (default value "simpleError"), that is to be signaled by an input validation error.

New signature should be

firmly(.f, ..., .checklist = list(),
       .warn_missing = character(), .error_class = character())

Distinguish between closures and functions-in-general

To match naive expectations, vld_function should correspond to the predicate base::is.function rather than purrr::is_function. A new checker vld_closure should take the place of vld_function. Thus:

vld_function <- localize("Not function" ~ is.function)
vld_closure  <- localize("Not closure" ~ purrr::is_function)

Reassign numeric local checkers

purrr::is_numeric and purrr::is_scalar_numeric are deprecated in purrr (>= 0.2.2.9000) since they don't check what you might naively think; in particular, purrr::is_numeric yields TRUE for factors.

Replace these predicates by their base R equivalents in the definitions of vld_numeric and vld_scalar_numeric.

Check item scope must be distinct from the scope of ambient formula

When creating a firm closure, say

foo <- firmly(f, list(check_item) ~ predicate)

the predicate function is to be evaluated in the environment of the formula list(check_item) ~ predicate.

The scope of evaluation of the right-hand side of the formula check_item is a bit more delicate: the environment of the rhs of check_item must be in scope, with precedence given, however, to arguments of f, which are then found in an isolated (temporary) environment of promise objects. (In the “Tidy Evaluation” framework, we would say that the arguments overscope the context of the check item.)

Thus, when validating a check item, three environments are in play:

  1. An environment of promises (the arguments of f as they are called).
  2. The environment of check_item.
  3. The environment of the formula list(check_item) ~ predicate.

These are generally not linearly ordered, so creating the proper transformation of the validation expression, and the proper environment in which to evaluate it, requires care. Thus, check_item is a quosure, in the tidy-eval sense.

One approach to ensuring that the validation occurs with the proper lexical scoping is as follows:

  • Tabluate the check-item environments in the data frame of checks (currently, only the environment of the check formula is recorded)
  • Modify the validation environment so that its parent is the environment of the formula check_item.
  • Bind the predicate(s) and the temporary environment of promises (whose parent is the environment of f) to the validation environment, which should contain no other bindings. (Potential name clashes in higher in the chain of environments can be virtually eliminated by using randomly generated names.)

For example, if we were to do this

a <- 1
is_positive <- function(.) . > 0
foo <- firmly(function(x) NULL, list(~x - a) ~ is_positive)

then the validation of the condition “is_positive(x - a)” should be translated to something like

`__PREDICATE__`(get("x", envir = `__PROMISES__`) - a)

and evaluated in an environment containing exactly two bindings, __PREDICATE__, __PROMISES__, and whose parent is the environment of the check item ~x - a. The binding __PREDICATE__ gets the predicate function is_positive, while the binding __PROMISES__ gets a (temporary) environment of promises of f.

In this example, the environment of the check item and the environment of the ambient formula are identical; in general, however, they could be quite different, e.g., localized check makers created in a package should not, when called, endow their check items with the same environment.

Alternative approach
Keep the current validation-evaluation framework as is, but allow check items to be quosures, cf. #39.

Error message from validate() should display name of test object

Example:

validate(mtcars,
         vld_all(~sapply(., is.numeric)),
         ~{nrow(.) > 1000},
         vld_all(~c("mpg", "cylinders") %in% names(.)))

#> Error: validate(. = .)
#> 1) FALSE: (function(.) {nrow(.) > 1000})(.)
#> 2) Not all TRUE: c("mpg", "cylinders") %in% names(.)

The error message should display the data frame (mtcars), rather than simply .. (It will be tricky to get this to work in a magrittr %>%-line, because of the way magrittr reduces a chain of calls.)

String interpolation for error messages

Fixed strings are used to specify custom error messages for check items, e.g., "x is not a scalar" ~ x. However, an equally simple yet more flexible error-message mechanism is possible using string interpolation, cf. Python PEP 498, the glue package, the str_interp() function from the stringr package.

For example, a check item like

"((.)) is not a scalar (its length is {length(.)})" ~ x

ought to produce the error message

"x is not a scalar (its length is 3)"

if x were a vector of length 3. (Would be nice to be able to use {{.}} rather than ((.)) to do deparse(substitute(.)), but that is already interpreted as a literal symbol, in glue.)

Enable warning of absence of specific arguments

Presently, the .warn_missing argument of firmly() behaves as a global toggle: you can either enable a warning for the absence of each and every argument (without default value), or not at all.

The capability to activate this on a per-argument basis would be more practical.

Add base R predicate checkers

In addition to purrr-based checkers such as vld_scalar_logical(), we should also include checkers based on the most useful predicates from base R (as a user convenience), e.g., vld_matrix() which would enforce a check based on the predicate is.matrix().

The method for implementing and documenting these functions should be highly automatic.

Turn on %checkin%, %checkout%

  1. Create an operator %checkout% to return output-validated functions.
  2. Rename %secure% to %checkin%.

It would then be possible to write

chk_input %checkin% f %checkout% chk_output

to elevate f to an end-to-end validated function.

Call in error message should print default arguments

Currently, this happens:

f <- function(x, y = "1") NULL
firmly(f, ~is.numeric)(1)
#> Error: firmly(f, ~is.numeric)(x = 1)
#> FALSE: is.numeric(y)

The error message is correct, but needs to be more informative: the full call, including default values, should be included. (Otherwise, it is a bit mysterious as to what y is, if you don't have f right in front of you.)

Replacement operators for firm closures

These would accompany the component extractors firm_core(), firm_checks(), firm_args(), firm_error(). They should apply only to firm closures.

Example: Replace the (input validation) checks of one function by those of another

f <- firmly(function(x, y) NULL, ~is.numeric) 
g <- firmly(function(x, y) NULL, ~is.character)

firm_checks(f) <- firm_checks(g)

Use dots_definitions() to capture checks

The error message needs to capture its calling environment, because it may need to be interpolated by glue(). Therefore, we need to use dots_definitions(), rather than quos(), when capturing checks (in either firmly() or fasten()).

Corrected API should look like

backtick <- function(x) encodeString(x, quote = "`")
msg <- "{{backtick(.)}} is not positive"

f <- function(x, y) NULL

# Don't need to unquote `msg` because it will be tidily evaluated by the parser
firmly(f, is.numeric, msg := {isTRUE(. > 0} ~ quos(x, x - y))

Consolidate/cull import dependencies

The functionality required of purrr and lazyeval is consolidated in rlang. Aside from the possible use of glue for string interpolation, rlang should be valaddin's sole dependency, so that the complete dependency graph is

base R <~ rlang <~ valaddin

This would make valaddin more suitable as a foundation for package writers.

Input-validation environment must encompass promises and predicates

Here's an example of a problem that arises in the current implementation of valaddin when the function environment is not on the search path of the temporary environment created in which inputs are validated (the input-validation environment):

# This simulates the creation of a function in a package
foo <- (function() {
  internal <- "internal"
  function(x = internal) x
})()
parent.env(environment(foo)) <- baseenv()

foo()
#> [1] "internal"

valaddin::firmly(foo, ~is.character)()
#> Error: (valaddin::firmly(foo, ~is.character))()
#> Error evaluating check is.character(x): object 'internal' not found

The result should have been the same as foo(). Why couldn't firmly find the object internal?

The reason is that internal is inaccessible from the context of input validation. The enclosure of the input-validation environment is the environment of the formula ~is.character, which is the global environment. However, the search path of the global environment intersects the search path of foo at the base environment, which is the enclosing environment of the environment that binds internal. This is why firmly can't find the object internal.

With that understanding, the problem can be resolved with the following construction:

  • Create a temporary environment with binds an environment of promises, and perform the input validation there, after pointing the enclosure of this temporary environment to the environment of the check formula.

NB: Because of R's lexical scoping rules for promises with default value vs promises with caller-supplied value, it is necessary that the enclosing environment of the promise environment be the environment of the function.

Mockup of refined input-validation procedure:

chk <- ~is.character

# Temporary environment in which to validate inputs
ve <- new.env(parent = emptyenv()) 
# ve has a single binding (the environment of promises), randomly named
.PROMENV <- sprintf("__PROM.ENV__%.12f", runif(1L))
ve[[.PROMENV]] <- (function(x = internal) environment())()
parent.env(ve[[.PROMENV]]) <- environment(foo)

# Evaluating the input check in ve now works
expr <- substitute(lazyeval::f_eval(chk)(get("x", ENV), list(ENV = as.name(.PROMENV)))
# Vary the enclosing environment of `ve` according to the check formula
parent.env(ve) <- lazyeval::f_env(chk)
eval(expr, ve)
#> [1] TRUE

In the actual procedure, the verbose eval line would be the result of simple substitution, which would transform the expression

quote(lazyeval::f_eval(chk)(x))

to the expression of the form

quote((lazyeval::f_eval(chk))(get("x", `__PROM.ENV__0.805840510409`)))

i.e., substitution on cdr of the call expression. (NB. It is important to use get rather than $ to access environment bindings, otherwise missing promises would return (empty) symbols.)

Remark: The proposed fix works "almost surely," but is not guaranteed to work unconditionally, for the name of the promise environment in ve—the value of the randomly generated string .PROMENV—could, in theory, clash with a name in the lexical scope of chk. The probability of this happening accidentally is practically nil. (A more complex validation mechanism could guarantee zero name collisions, at the expense of performance and a considerably more complicated parsing-substitution procedure.)

Evaluate check items as quosures

This is already working with lazyeval::f_list() and lazyeval::f_new(), but should be replaced by equivalent functions in rlang (quos(), new_quosure(), resp.).

Grouping parameters with validation declarations?

Valaddin has exactly the semantics I've been looking for – the only drawback is that I can't find a way to leverage it without sacrificing readability and locality of semantics in code. When I'm defining a function in an explicitly typed language, it's easy for someone to see the intent by reading the function signature:

def doSomething(a: Numeric, b: Numeric): Numeric = ...

With valaddin, I'm stuck doing something like this:

doSomething <- function(a, b) {
  ...
  ... potentially many lines of code in between ...
  ...
 }

doSomething %<>% firmly(list(~a, ~b) ~ { is.numeric(a) && is.numeric(b)})`

I'm reluctant to adopt any convention that forces the reader to go all the way past the end of a function to find out the key information they need to understand its arguments. The only option I've come up with would be introducing a "reverse" magrittr-style %<% operator that pulls from right-to-left (haven't tried this, just hypothetically):

doSomething <- firmly(list(~a, ~b) ~ { ... }) %<% function(a, b) {
  ... function body goes here ...
}

Apologies if this is stretching the nature of "Issue" into "Stylistic Grievance", but I really like Valaddin's design & want to adopt it. Is there anything you can suggest to make it possible to co-locate the function signature/declaration and the declaration of the semantic constraints, or at least allow them to be close enough that the reader's job of understanding the code isn't frustrated by not knowing what semantic expectations may or may not be intended by the code's author?

Adverb "strictly" is confusing

The function name "strictly", resp. "nonstrictly", is confusingly at odds with the use of that word in CS, where it is (essentially) a synonym for "applicative-order"/"strict", resp. "normal-order"/"non-strict", evaluation, which concerns something altogether different from input validation.

Rename strictly() and nonstrictly().

Enable localized check makers for quosure-checks

This issue refers to the development branch.

Example:

chkrs <- localize(isTRUE, "{{.}} not scalar (length is {length(.)})" = {length(.) == 1L})

should produce the list of two functions such that

chkrs[[1]](x, y)
#> ~isTRUE ~ list(`FALSE: isTRUE(x)` = ~x, `FALSE: isTRUE(y)` = ~y)

chkrs[[2]](x, "Not scalar" = y)
#> ~{length(.) == 1L} ~ list(`x not scalar (length is {length(.)})` = ~x, `Not scalar` = ~y)

NB It is crucial that the predicate remains a quosure—it must remember its original scope.

As such, the error message won't be properly interpreted by the current implementation of err_invalid_input(), because that function relies on the flag dot_as_expr which is set by parse_check(), and is invisible in the output, above.

Consequently, the mechanism by which the proper scope of dot-interpolation is identified will need to be adapted to the above behavior of localize().

  • Solution 1: One possibility would be substitute the function of the boolean flag dot_as_expr with a special (prefix/suffix) “marker” in the error message, which is interpreted by err_invalid_input().

  • Solution 2: Alternatively, a simpler, but arguably more “hacky,” approach would be endow localized checkers with an additional “global name” attribute that would be interpreted by parse_check() as overriding the value of the argument msg. In this case, it would suffice for chkrs to behave accordingly:

    chkrs[[1]](x, y)
    #> ~isTRUE ~ list(~x, ~y)
    #> attr(,"global_name")
    #> [1] ""
    
    chkrs[[2]](x, "Not scalar" = y)
    #> ~{length(.) == 1L} ~ list(~x, `Not scalar` = ~y)
    #> attr(,"global_name")
    #> [1] "{{.}} not scalar (length is {length(.)})"

    No change would then be required elsewhere, and the logic of localize() would be simplified as well, for it would not require any naming logic.

Parameterized localized check makers

Examples:

  • vld_identical(2L, ~length(x))
  • vld_equal(2, ~length(x))
  • vld_inherits("simpleError", ~cnd)

It might be useful to have a general constructor for comparison predicates (although, technically, every predicate is a comparison predicate with respect to isTRUE).

strictly() respects lazy evaluation

Requirement: When all checks pass, a strictly applied function behaves identically to the underlying (non-strict) function. In particular, all arguments should be evaluated lazily, even if they undergo input validation.

Verify this requirement with unit tests.

Implicitly globalize localized predicate

If a localized predicate is not localized on any expressions, it should be implicitly regarded as a global predicate. For example, instead of having to write the verbose expression

firmly(f, globalize(vld_inherits("myClass")))

you should be able to just write

firmly(f, vld_inherits("myClass"))

%firmly%

Cf. #19

An operator to provide alternative semantics for check specification (idea due to @MilkWasABadChoice):

`%firmly%` <- function(.checklist, .f) firmly(.f, .checklist = .checklist)

Example:

vec_add <- list(
  ~is.numeric, vld_true(~length(a) == length(b))
) %firmly% 
  function(a, b) {
    a + b
  }

Boolean algebra for check formulae

Define a generic operator isnt which negates check formulae and (localized) check makers. Should take into account that the predicate may be expressed as a lambda function.

link to assertive in README

Any chance of a link to the assertive package in the Related Packages section of the README?

  • assertive provides over 400 functions for checking common code problems. It is optimized for clarity of code and clarity of error messages.

You might also want to link to Michel Lang's checkmate package.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.