valaddin

Functional input validation to make R functions more readable and robust

https://github.com/egnha/valaddin

Science Score: 13.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (15.8%) to scientific vocabulary

Keywords

data-validation input-validation r type-safety
Last synced: 6 months ago · JSON representation

Repository

Functional input validation to make R functions more readable and robust

Basic Info
  • Host: GitHub
  • Owner: egnha
  • License: other
  • Language: R
  • Default Branch: dev
  • Homepage:
  • Size: 1.04 MB
Statistics
  • Stars: 34
  • Watchers: 5
  • Forks: 1
  • Open Issues: 1
  • Releases: 2
Topics
data-validation input-validation r type-safety
Created about 9 years ago · Last pushed over 2 years ago
Metadata Files
Readme Changelog License

README.Rmd

---
output: github_document
---



```{r, echo = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment  = "#>",
  fig.path = "README-"
)
```

**Development has moved to [rong](https://github.com/egnha/rong)**

# valaddin



[![R-CMD-check](https://github.com/egnha/valaddin/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/egnha/valaddin/actions/workflows/R-CMD-check.yaml) [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/valaddin)](https://cran.r-project.org/package=valaddin) [![stability-frozen](https://img.shields.io/badge/stability-frozen-blue.svg)](https://github.com/emersion/stability-badges#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](https://cran.r-project.org/package=valaddin)

```{r, eval = FALSE}
install.packages("valaddin")
```

or get the development version from GitHub using the [devtools](https://github.com/r-lib/devtools) package

```{r, eval = FALSE}
# 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()`

```{r}
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:

```{r, error = TRUE, purl = FALSE}
secant_numeric <- function(f, x, dx) {
  stopifnot(is.numeric(x), is.numeric(dx))
  secant(f, x, dx)
}

secant_numeric(log, 1, .1)

secant_numeric(log, "1", ".1")
```

### 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.

```{r, error = TRUE, purl = FALSE}
library(valaddin)

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

secant(log, 1, .1)

secant(log, "1", ".1")
```

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

```{r, error = TRUE, purl = FALSE}
secant <- firmly(secant, list(~x, ~dx) ~ {length(.) == 1L})

secant(log, "1", c(.1, .01))
```

Or, alternatively, all in one go:

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

secant(log, 1, .1)

secant(log, "1", c(.1, .01))
```

### 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 ` ~ `. 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:

```{r, error = TRUE, purl = FALSE}
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)

bc_uhp(.5, -.2)
```

#### Easily apply a check to all arguments

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

```{r, error = TRUE, purl = FALSE}
bc_num <- firmly(bc, ~is.numeric)

bc_num(.5, ".2")

bc_num(".5", ".2")
```

Or fill in a custom error message:

```{r, error = TRUE, purl = FALSE}
bc_num <- firmly(bc, "Not numeric" ~ is.numeric)

bc_num(.5, ".2")
```

#### Check conditions with multi-argument dependencies

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

```{r, error = TRUE, purl = FALSE}
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)

bc_tri(.5, .6)
```

### 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:

    ```{r, error = TRUE, purl = FALSE}
    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)

    bc(.5, c(.2, .1))

    bc(".5", 1)
    ```

-   Use the [magrittr](https://github.com/tidyverse/magrittr) `%>%` operator to deliver input checks, by capturing them as a list with `firmly()`'s `.checklist` argument:

    ```{r, message = FALSE}
    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:

    ```{r}
    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](https://cran.r-project.org/package=valaddin/vignettes/valaddin.html) for an overview of use cases.

## Related packages

-   [assertive](https://bitbucket.org/richierocks/assertive), [assertthat](https://github.com/hadley/assertthat), and [checkmate](https://github.com/mllg/checkmate) provide handy collections of predicate functions that you can use in conjunction with `firmly()`.

-   [argufy](https://github.com/gaborcsardi/argufy) takes a different approach to input validation, using [roxygen](https://github.com/r-lib/roxygen2) comments to specify checks.

-   [ensurer](https://github.com/smbache/ensurer) and [assertr](https://github.com/ropensci/assertr) provide a means of validating function values. Additionally, ensurer provides an experimental replacement for `function()` that builds functions with type-validated arguments.

-   [typeCheck](https://github.com/jimhester/typeCheck), together with [Types for R](https://github.com/jimhester/types), 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](https://github.com/egnha)

Owner

  • Name: Eugene
  • Login: egnha
  • Kind: user

GitHub Events

Total
  • Watch event: 1
Last Year
  • Watch event: 1

Committers

Last synced: almost 3 years ago

All Time
  • Total Commits: 651
  • Total Committers: 2
  • Avg Commits per committer: 325.5
  • Development Distribution Score (DDS): 0.017
Top Committers
Name Email Commits
Eugene Ha e****a@p****e 640
Eugene Ha 1****a@u****m 11
Committer Domains (Top 20 + Academic)

Issues and Pull Requests

Last synced: about 1 year ago

All Time
  • Total issues: 59
  • Total pull requests: 0
  • Average time to close issues: about 2 months
  • Average time to close pull requests: N/A
  • Total issue authors: 5
  • Total pull request authors: 0
  • Average comments per issue: 0.81
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 0
  • Pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Issue authors: 0
  • Pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • egnha (55)
  • jbao (1)
  • richierocks (1)
  • MilkWasABadChoice (1)
  • lionel- (1)
Pull Request Authors
Top Labels
Issue Labels
bug (3) maintenance (1) enhancement (1) question (1)
Pull Request Labels

Packages

  • Total packages: 1
  • Total downloads:
    • cran 192 last-month
  • Total dependent packages: 0
  • Total dependent repositories: 0
  • Total versions: 5
  • Total maintainers: 1
cran.r-project.org: valaddin

Functional Input Validation

  • Versions: 5
  • Dependent Packages: 0
  • Dependent Repositories: 0
  • Downloads: 192 Last month
Rankings
Stargazers count: 9.6%
Forks count: 21.9%
Dependent packages count: 29.8%
Average: 33.3%
Dependent repos count: 35.5%
Downloads: 69.7%
Maintainers (1)
Last synced: about 1 year ago

Dependencies

DESCRIPTION cran
  • R >= 3.1.0 depends
  • lazyeval >= 0.2.1 imports
  • knitr * suggests
  • magrittr * suggests
  • rmarkdown * suggests
  • stringr * suggests
  • testthat * suggests
.github/workflows/R-CMD-check.yaml actions
  • actions/checkout v3 composite
  • r-lib/actions/check-r-package v2 composite
  • r-lib/actions/setup-pandoc v2 composite
  • r-lib/actions/setup-r v2 composite
  • r-lib/actions/setup-r-dependencies v2 composite