Skip to contents

This function takes any number of named expressions referring to objects in the given environment, checking and possibly coercing them to the specified type and/or size, and also checking them against any number of validation functions/formulas. Using the function keywords of validate, cast, lossy_cast, recycle, and coerce within the expressions allows for different behaviours:

  • validate: checks that the object is of the specified type/size and adheres to the validations, throwing an error if not.

  • cast: differs from validate by checking that the object is of the specified type, and if not attempts to cast it to that type (throwing an error if not possible).

  • lossy_cast: differs from cast by allowing lossy casting (e.g. double to integer).

  • recycle: differs from validate by checking that the object is of the specified size, and if not attempts to recycle it to that size (throwing an error if not possible).

  • coerce: differs from validate by checking both the type and size, and attempting to cast and/or recycle it to that type/size (throwing an error if not possible). Casting is not lossy by default but can be made lossy by adding lossy = TRUE within the coerce() call.

Usage

restrict(..., .env = caller_env(), .class = NULL, .error_call = caller_env())

Arguments

...

any number of named R expressions, with the names referring to objects in the environment specified by the .env argument, and the expressions built using the functions: validate(), cast(), lossy_cast(), recycle(), and coerce().

.env

the environment to use for the evaluation of the expressions & the (possible) assignment of the variables. Cannot be the global environment.

.class

class to assign to the error (passed to rlang::abort).

.error_call

the call environment to use for the error (passed to rlang::abort).

Details

These functions accept the named arguments type, size and mask (lossy is also accepted within coerce()):

All other inputs should be unnamed validations: either expressions or formulas (that evaluate to logical). restrict first evaluates type, then size, then the validations. Any change from the prior expression is reflected in subsequent expressions, i.e. if an object is cast to a new type then that new type is used for the size check and validations. If you do not wish to use the vctrs type/size checking, then instead give validations functions such as ~ is.integer(.x). However, these will only validate, not cast or recycle. restrict is designed for the checking of numerous objects, for a smaller number of objects to check see the abort_if_not, cast_if_not, recycle_if_not, schema, schema_cast and schema_recycle functions.

Examples

# Will not alter the global environment so most examples here are wrapped with local().
x <- 1L
restrict(x = validate(type = integer())) |> try()
# => Error : Argument `.env` cannot be the global environment.

local({
  x <- 1L
  restrict(x = coerce(type = double(), size = 3))
  cat(class(x), length(x), sep = ", ")
})
#> numeric, 3

local({
  x <- 1.5
  restrict(x = cast(type = integer())) |> try()
})
#> Error in eval(quote({ : Error in `restrict()`
#>  Can't convert from `x` <double> to <integer> due to loss of precision. 
#>   Locations: 1
# => Error : Can't convert from `x` <double> to <integer> due to loss of precision.

local({
  x <- 1.5
  restrict(x = lossy_cast(type = integer()))
  cat(x, class(x), sep = ", ")

  # or

  x <- 1.5
  restrict(x = coerce(type = integer(), lossy = TRUE))
  cat(x, class(x), sep = ", ")
})
#> 1, integer1, integer

# other objects can be used as the type to cast to or size to recycle to, e.g.:
local({
  x <- 1L
  y <- 2.3
  z <- 3L
  restrict(x = coerce(type = y, size = z))
  cat(class(x), length(x), sep = ", ")
})
#> numeric, 3

# restrict works sequentially, so references to objects will be
# after they have been evaluated:
local({
  x <- y <- 1L
  restrict(
    x = cast(type = double()),
    y = cast(type = x)
  )
  cat(class(x), class(y), sep = ", ")
})
#> numeric, numeric

# numerous validations can be given and type and size checking can be done
# within if base R checking is preferred:
local({
  x <- 1L
  restrict(
    x = validate(
      ~ is.integer(.x),
      ~ length(.x) == 1,
      \(y) all(y > 0),
      \(z) !is.character(z)
    )
  )
})

# the `.env` argument determines the expression and assignment environment:
local({
  x <- 1L
  e <- new.env()
  e$x <- 1L
  restrict(x = cast(type = 1.5), .env = e)
  cat(class(e$x), class(x), sep = ", ")
})
#> numeric, integer

# names (lhs) are checked to be in the `.env` environment, throwing an error if not found:
local({
  x <- 1L
  e <- new.env()
  restrict(x = cast(type = 1.5), .env = e) |> try()
})
#> Error in eval(quote({ : Error in `restrict()`
#>  Objects `x` are not found in the `.env` environment specified.
# => Error: Objects `x` are not found in the `.env` environment specified.

# for expressions (rhs), the `.env` argument is preferentially chosen, but if not found
# then the normal R scoping rules apply:
local({
  x <- 1.5
  e <- new.env()
  e$z <- 1L
  restrict(x = cast(type = x), .env = e) |> try()
  cat(class(e$z))
})
#> Error in eval(quote({ : Error in `restrict()`
#>  Objects `x` are not found in the `.env` environment specified.
#> integer