| Title: | Runtime Type Checking |
|---|---|
| Description: | Provides a lightweight runtime type system for 'R' that enables developers to declare and enforce variable types during execution. Inspired by 'TypeScript', the package introduces intuitive syntax for annotating variables and validating data structures, helping catch type-related errors early and making 'R' code more robust and easier to maintain. |
| Authors: | Mohamed El Fodil Ihaddaden [aut, cre] |
| Maintainer: | Mohamed El Fodil Ihaddaden <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.1 |
| Built: | 2026-06-04 11:11:17 UTC |
| Source: | https://github.com/feddelegrand7/sicher |
Creates a type that checks for a specific vector length.
## S3 method for class 'sicher_type' type[size]## S3 method for class 'sicher_type' type[size]
type |
A sicher_type object |
size |
The required length (non-negative integer) |
A new sicher_type that checks for the specified length
vec %:% Numeric[3] %<-% c(1, 2, 3) try(vec <- c(1, 2)) # Error: wrong length try(vec <- c("a", "b", "c")) # Error: wrong typevec %:% Numeric[3] %<-% c(1, 2, 3) try(vec <- c(1, 2)) # Error: wrong length try(vec <- c("a", "b", "c")) # Error: wrong type
Creates a typed variable annotation used together with '%<-%'.
name %:% typename %:% type
name |
Variable name (unevaluated). |
type |
Type specification (e.g., Integer, String, Double). |
A typed annotation object.
x %:% Integer %<-% 5L name %:% String %<-% "Alice" id %:% (Integer | String) %<-% 42Lx %:% Integer %<-% 5L name %:% String %<-% "Alice" id %:% (Integer | String) %<-% 42L
Completes the typed assignment started with '%:%'.
typed_annotation %<-% valuetyped_annotation %<-% value
typed_annotation |
Result of '%:%'. |
value |
Value to assign. |
Invisibly returns the assigned value.
x %:% Integer %<-% 5L y %:% Double %<-% 3.14 name %:% String %<-% "Bob" flag %:% Bool %<-% TRUEx %:% Integer %<-% 5L y %:% Double %<-% 3.14 name %:% String %<-% "Bob" flag %:% Bool %<-% TRUE
A type that accepts any value.
AnyAny
An object of class sicher_type of length 2.
Creates a type that accepts numeric values within the closed interval
[min, max]. Every element of a vector must satisfy the bounds.
NA values are always rejected.
Between(min, max)Between(min, max)
min |
A single non-NA numeric scalar giving the lower bound (inclusive). |
max |
A single non-NA numeric scalar giving the upper bound (inclusive). |
A new sicher_type that checks the value is numeric, NA-free,
and that all elements lie within [min, max].
age %:% Between(0, 150) %<-% 30 score %:% Between(0.0, 1.0) %<-% 0.95 try(score <- 1.5) # Error: 1.5 is outside [0, 1] try(score <- NA_real_) # Error: value contains NA(s) # Works with integer storage (is.numeric(1L) is TRUE in R) count %:% Between(0L, 100L) %<-% 42Lage %:% Between(0, 150) %<-% 30 score %:% Between(0.0, 1.0) %<-% 0.95 try(score <- 1.5) # Error: 1.5 is outside [0, 1] try(score <- NA_real_) # Error: value contains NA(s) # Works with integer storage (is.numeric(1L) is TRUE in R) count %:% Between(0L, 100L) %<-% 42L
A type that checks for logical vectors.
BoolBool
An object of class sicher_type of length 2.
Validates that a value conforms to a specified type. This is the core validation function used internally by the type system, but can also be called directly for manual type checking.
check_type(value, type, context = NULL)check_type(value, type, context = NULL)
value |
The value to check |
type |
A sicher_type, sicher_union, or sicher_readonly object |
context |
Optional character string describing where the check is occurring (used in error messages) |
This function:
Checks if a value matches a type specification
Handles union types (checks if value matches any type in the union)
Handles readonly types (strips the readonly modifier before checking)
Provides detailed error messages when checks fail
Returns 'TRUE' invisibly if the value matches the type, otherwise throws an error with a descriptive message.
create_type for creating custom types
# Direct type checking check_type(5L, Integer) # Returns TRUE try(check_type("hello", Integer)) # Throws error # With context for better error messages try(check_type(5L, String, context = "user_name")) # With union types check_type(5L, Integer | String) # Returns TRUE try(check_type(5.5, Integer | String)) # Throws error# Direct type checking check_type(5L, Integer) # Returns TRUE try(check_type("hello", Integer)) # Throws error # With context for better error messages try(check_type(5L, String, context = "user_name")) # With union types check_type(5L, Integer | String) # Returns TRUE try(check_type(5.5, Integer | String)) # Throws error
Builds a type that validates a data frame's column names and their types. Each column is treated as a vector and checked against the provided sicher_type (or union) specification. Optional columns may be declared with 'Optional()'.
create_dataframe_type(col_spec)create_dataframe_type(col_spec)
col_spec |
A named list where names are column names and values are sicher_type or sicher_union objects describing the expected column type. |
A sicher_type representing the data frame schema.
PersonDF <- create_dataframe_type(list( name = String, age = Numeric, height = Optional(Numeric) )) df %:% PersonDF %<-% data.frame( name = c("Alice", "Bob"), age = c(25, 30) )PersonDF <- create_dataframe_type(list( name = String, age = Numeric, height = Optional(Numeric) )) df %:% PersonDF %<-% data.frame( name = c("Alice", "Bob"), age = c(25, 30) )
Creates a type that checks for lists with specific named elements and their types. Similar to object types in TypeScript/JavaScript.
create_list_type(type_spec)create_list_type(type_spec)
type_spec |
A named list where names are field names and values are sicher_type objects |
A sicher_type that validates list structure
# Define a User type User <- create_list_type(list( name = String, age = Numeric, preferences = create_list_type(list( color = String, movie = String )) )) # Use it user %:% User %<-% list( name = "Alice", age = 25, preferences = list(color = "red", movie = "batman") )# Define a User type User <- create_list_type(list( name = String, age = Numeric, preferences = create_list_type(list( color = String, movie = String )) )) # Use it user %:% User %<-% list( name = "Alice", age = 25, preferences = list(color = "red", movie = "batman") )
Creates a new type object for use in the type checking system. A type consists of a name (for error messages) and a checker function (for validation).
create_type(name, checker)create_type(name, checker)
name |
A single character string representing the type name. This name will be displayed in error messages when type checking fails. |
checker |
A function that takes a single argument and returns 'TRUE' if the value matches the type, 'FALSE' otherwise. The checker function should be a predicate (e.g., 'is.numeric', 'is.character'). |
This is the fundamental building block of the type system. Built-in types like 'Integer', 'Double', and 'String' are all created using this function.
The checker function should:
Accept a single argument (the value to check)
Return 'TRUE' if the value is valid for this type
Return 'FALSE' if the value is invalid
Not throw errors (error handling is done by 'check_type')
An object of class '"sicher_type"' containing:
The type name as a character string
The checker function
check_type for type validation,
Scalar for creating scalar type variants,
Readonly for creating readonly type variants
# Create a custom positive number Positive <- create_type("positive", function(x) { is.numeric(x) && all(x > 0) }) # Use it in type annotations age %:% Positive %<-% 25 try(age <- -5) # Error: Type error # Create a custom email type Email <- create_type("email", function(x) { is.character(x) && length(x) == 1 && grepl("^[^@]+@[^@]+\\.[^@]+$", x) }) user_email %:% Email %<-% "[email protected]" # Create a type for even integers EvenInt <- create_type("even_int", function(x) { is.integer(x) && all(x %% 2 == 0) }) value %:% EvenInt %<-% 4L try(value <- 5L) # Error: Type error # Create a type that checks data frame structure PersonDF <- create_type("person_df", function(x) { is.data.frame(x) && all(c("name", "age") %in% names(x)) && is.character(x$name) && is.numeric(x$age) })# Create a custom positive number Positive <- create_type("positive", function(x) { is.numeric(x) && all(x > 0) }) # Use it in type annotations age %:% Positive %<-% 25 try(age <- -5) # Error: Type error # Create a custom email type Email <- create_type("email", function(x) { is.character(x) && length(x) == 1 && grepl("^[^@]+@[^@]+\\.[^@]+$", x) }) user_email %:% Email %<-% "[email protected]" # Create a type for even integers EvenInt <- create_type("even_int", function(x) { is.integer(x) && all(x %% 2 == 0) }) value %:% EvenInt %<-% 4L try(value <- 5L) # Error: Type error # Create a type that checks data frame structure PersonDF <- create_type("person_df", function(x) { is.data.frame(x) && all(c("name", "age") %in% names(x)) && is.character(x$name) && is.numeric(x$age) })
A type that checks for data.frame objects.
DataFrameDataFrame
An object of class sicher_type of length 2.
A type that checks for double-precision numeric vectors.
DoubleDouble
An object of class sicher_type of length 2.
Creates an enumeration type using regular function call syntax. The resulting type only accepts atomic vectors whose elements all belong to the declared set of allowed values.
Enum(...)Enum(...)
... |
Allowed scalar values or a single atomic vector of allowed values. |
A new sicher_type that checks all values belong to the enum.
status %:% Enum(1, 2, 3) %<-% 2 colors %:% Enum("red", "green", "blue") %<-% c("red", "blue") try(colors <- c("yellow", "red"))status %:% Enum(1, 2, 3) %<-% 2 colors %:% Enum("red", "green", "blue") %<-% c("red", "blue") try(colors <- c("yellow", "red"))
Creates a new list type by merging the field specification of an existing
create_list_type() type with a set of additional fields. Analogous
to TypeScript interface extension (interface Employee extends Person).
extend(base, extra)extend(base, extra)
base |
A |
extra |
A named list of additional fields in the same format accepted
by |
A new sicher_type whose required and optional fields are the
union of base's fields and extra's fields. Fields in
extra that share a name with a field in base override
the base field's type (with a warning so the shadowing is never silent).
Person <- create_list_type(list( name = String, age = Numeric )) # Basic extension Employee <- extend(Person, list( role = String, department = Optional(String) )) emp %:% Employee %<-% list(name = "Alice", age = 30, role = "Engineer") # Multi-level extension Manager <- extend(Employee, list( reports = Numeric # number of direct reports )) mgr %:% Manager %<-% list( name = "Bob", age = 45, role = "VP", reports = 12 ) # Field override (emits a warning) DetailedPerson <- extend(Person, list( age = Integer # narrows Numeric -> Integer ))Person <- create_list_type(list( name = String, age = Numeric )) # Basic extension Employee <- extend(Person, list( role = String, department = Optional(String) )) emp %:% Employee %<-% list(name = "Alice", age = 30, role = "Engineer") # Multi-level extension Manager <- extend(Employee, list( reports = Numeric # number of direct reports )) mgr %:% Manager %<-% list( name = "Bob", age = 45, role = "VP", reports = 12 ) # Field override (emits a warning) DetailedPerson <- extend(Person, list( age = Integer # narrows Numeric -> Integer ))
A type that checks for function objects.
FunctionFunction
An object of class sicher_type of length 2.
Infers the most appropriate sicher type constructor for a given R object. By default, inference focuses on the underlying type and does not lock in the observed length of vectors. Set 'strict = TRUE' to also infer scalar and fixed-size vector constraints from the example value.
infer_type(obj, strict = FALSE)infer_type(obj, strict = FALSE)
obj |
Any R object (primitive, vector, list, data.frame, function, etc.) |
strict |
Logical scalar. When 'FALSE' (default), infer only the base type shape, such as 'Numeric', 'String', 'ListOf(Integer)', or a 'create_dataframe_type()' schema without fixed lengths. When 'TRUE', also infer 'Scalar()' and '[n]' size constraints from the observed object. |
A sicher_type object (e.g., Numeric, String, create_list_type(...), ListOf(...), etc.)
infer_type(42L) # Integer infer_type(3.14) # Double infer_type(c(1, 2, 3)) # Double or Numeric, no length constraint infer_type("abc") # String infer_type(c("a", "b")) # String infer_type(TRUE) # Bool infer_type(NULL) # Null infer_type(function(x) x + 1) # Function infer_type(list(a = 1, b = "x")) # create_list_type(list(a = Double, b = String)) infer_type(list(1, 2, 3)) # ListOf(Double) infer_type(data.frame(x = 1:3)) # create_dataframe_type(list(x = Integer)) infer_type(list(a = NULL, b = 1)) # create_list_type(list(a = Optional(Any), b = Double)) # Strict mode keeps observed length constraints infer_type(42L, strict = TRUE) # Scalar(Integer) infer_type(c("a", "b"), strict = TRUE) # String[2]infer_type(42L) # Integer infer_type(3.14) # Double infer_type(c(1, 2, 3)) # Double or Numeric, no length constraint infer_type("abc") # String infer_type(c("a", "b")) # String infer_type(TRUE) # Bool infer_type(NULL) # Null infer_type(function(x) x + 1) # Function infer_type(list(a = 1, b = "x")) # create_list_type(list(a = Double, b = String)) infer_type(list(1, 2, 3)) # ListOf(Double) infer_type(data.frame(x = 1:3)) # create_dataframe_type(list(x = Integer)) infer_type(list(a = NULL, b = 1)) # create_list_type(list(a = Optional(Any), b = Double)) # Strict mode keeps observed length constraints infer_type(42L, strict = TRUE) # Scalar(Integer) infer_type(c("a", "b"), strict = TRUE) # String[2]
A type that checks for integer vectors.
IntegerInteger
An object of class sicher_type of length 2.
A type that checks for list objects.
ListList
An object of class sicher_type of length 2.
Produces a type that validates a list whose every element satisfies the provided element type. This is useful when you expect a list of similar records (e.g. parsed JSON array). You can further constrain the length with the size operator: 'ListOf(User)[10]'.
ListOf(element_type)ListOf(element_type)
element_type |
A sicher_type or sicher_union describing each element. |
A sicher_type that checks the value is a list and that all elements conform to 'element_type'.
# Define an inner record type Record <- create_list_type(list(id = Numeric, name = String)) # Now require a list of records Records <- ListOf(Record) records %:% Records %<-% list( list(id = 1, name = "a"), list(id = 2, name = "b") ) # fixed-size list of ten records TenRecs <- Records[10] # will throw if length != 10# Define an inner record type Record <- create_list_type(list(id = Numeric, name = String)) # Now require a list of records Records <- ListOf(Record) records %:% Records %<-% list( list(id = 1, name = "a"), list(id = 2, name = "b") ) # fixed-size list of ten records TenRecs <- Records[10] # will throw if length != 10
Creates a literal type inspired by TypeScript literal unions. The resulting type only accepts scalar atomic values that exactly match one of the declared literals, including the underlying R storage mode. For example, '200' and '200L' are treated as different literals.
Literal(...)Literal(...)
... |
Allowed scalar atomic literal values. |
A new sicher_type that checks the value is exactly one of the declared literals.
direction %:% Literal("left", "right") %<-% "left" direction <- "right" direction <- "left" try(direction <- c("right", "left")) status_code %:% Literal(200, 404) %<-% 200 try(status_code <- 500)direction %:% Literal("left", "right") %<-% "left" direction <- "right" direction <- "left" try(direction <- c("right", "left")) status_code %:% Literal(200, 404) %<-% 200 try(status_code <- 500)
Creates a type that accepts only character vectors whose every element
matches the given Perl-compatible regular expression. NA elements are
always rejected. An empty character vector character(0) passes
vacuously; combine with NonEmpty() if you need at least one element.
Matches(pattern)Matches(pattern)
pattern |
A single non-NA character string used as a PCRE regex
(passed to |
A new sicher_type that validates every element against
pattern.
email %:% Matches("^[^@]+@[^@]+\\.[^@]+$") %<-% "[email protected]" try(email <- "not-an-email") # Error: does not match pattern hex %:% Matches("^#[0-9A-Fa-f]{6}$") %<-% "#FF5733" # Combine with NonEmpty to also require at least one element tags %:% NonEmpty(Matches("^[a-z]+$")) %<-% c("foo", "bar")email %:% Matches("^[^@]+@[^@]+\\.[^@]+$") %<-% "[email protected]" try(email <- "not-an-email") # Error: does not match pattern hex %:% Matches("^#[0-9A-Fa-f]{6}$") %<-% "#FF5733" # Combine with NonEmpty to also require at least one element tags %:% NonEmpty(Matches("^[a-z]+$")) %<-% c("foo", "bar")
Creates a type that first validates the underlying type, then rejects
empty values. For data frames "empty" means zero rows (nrow == 0);
for all other objects it means length == 0.
NonEmpty(type)NonEmpty(type)
type |
A |
A new sicher_type that rejects zero-length (or zero-row)
values after the base type check passes.
tags %:% NonEmpty(String) %<-% c("r", "types") try(tags <- character(0)) # Error: value must be non-empty items %:% NonEmpty(List) %<-% list(1, 2) try(items <- list()) # Error: value must be non-emptytags %:% NonEmpty(String) %<-% c("r", "types") try(tags <- character(0)) # Error: value must be non-empty items %:% NonEmpty(List) %<-% list(1, 2) try(items <- list()) # Error: value must be non-empty
Creates a type that accepts only non-NA values of the underlying type.
Any element equal to NA causes an immediate error, making silent NA
propagation impossible in typed pipelines.
NonNA(type)NonNA(type)
type |
A |
A new sicher_type that first validates the underlying type,
then rejects any value that contains at least one NA.
salary %:% NonNA(Numeric) %<-% c(1800, 2300, 4000) try(salary <- c(1800, NA, 4000)) # Error: value contains NA(s) # Composable with other modifiers tag %:% NonNA(Scalar(String)) %<-% "admin"salary %:% NonNA(Numeric) %<-% c(1800, 2300, 4000) try(salary <- c(1800, NA, 4000)) # Error: value contains NA(s) # Composable with other modifiers tag %:% NonNA(Scalar(String)) %<-% "admin"
A type that checks for NULL values.
NullNull
An object of class sicher_type of length 2.
A type that checks for numeric vectors (integer or double).
NumericNumeric
An object of class sicher_type of length 2.
Creates a type that accepts NULL values in addition to the base type.
Optional(type)Optional(type)
type |
A sicher_type object |
A union type that includes Null
middle_name %:% Optional(String) %<-% NULL middle_name <- "Marie" # Also OKmiddle_name %:% Optional(String) %<-% NULL middle_name <- "Marie" # Also OK
Print method for sicher_type
## S3 method for class 'sicher_type' print(x, ...)## S3 method for class 'sicher_type' print(x, ...)
x |
A sicher_type object |
... |
Additional arguments (ignored) |
Invisibly returns the input object
Print method for sicher_typed_annotation
## S3 method for class 'sicher_typed_annotation' print(x, ...)## S3 method for class 'sicher_typed_annotation' print(x, ...)
x |
A sicher_typed_annotation object |
... |
Additional arguments (ignored) |
Invisibly returns the input object
Print method for sicher_typed_function
## S3 method for class 'sicher_typed_function' print(x, ...)## S3 method for class 'sicher_typed_function' print(x, ...)
x |
A sicher_typed_function object |
... |
Additional arguments (ignored) |
Invisibly returns the input object
Print method for sicher_union
## S3 method for class 'sicher_union' print(x, ...)## S3 method for class 'sicher_union' print(x, ...)
x |
A sicher_union object |
... |
Additional arguments (ignored) |
Invisibly returns the input object
Creates a type that prevents reassignment after initial value is set.
Readonly(type)Readonly(type)
type |
A sicher_type object |
A readonly type modifier
PI %:% Readonly(Double) %<-% 3.14159 try(PI <- 3.0) # Error: cannot reassign readonlyPI %:% Readonly(Double) %<-% 3.14159 try(PI <- 3.0) # Error: cannot reassign readonly
Creates a type that only accepts single values (vectors of length 1).
Scalar(type)Scalar(type)
type |
A sicher_type object |
A new sicher_type that checks for length 1
age %:% Scalar(Integer) %<-% 30L try(age <- c(30L, 40L)) # Error: not scalarage %:% Scalar(Integer) %<-% 30L try(age <- c(30L, 40L)) # Error: not scalar
A type that checks for character vectors.
StringString
An object of class sicher_type of length 2.
Wraps a function with runtime type checking for its parameters and, optionally, its return value. This is the function counterpart to the typed variable operators ('%:%' / '%<-%'), providing a syntax analogous to typed function signatures:
add <- typed_function(
function(x, y) x + y,
params = list(x = Numeric, y = Numeric),
.return = Numeric
)
typed_function(fn, params = list(), .return = NULL)typed_function(fn, params = list(), .return = NULL)
fn |
The function to wrap. Its formals are preserved in the wrapper so callers use the exact same signature. |
params |
A named list mapping parameter names to their types
(e.g. |
.return |
Optional return type. When |
A function with the same formals as fn and S3 class
"sicher_typed_function" that:
Validates each listed parameter on every call.
Validates the return value when .return is specified.
Delegates all argument passing to fn unchanged.
# Basic typed function add <- typed_function( function(x, y) x + y, params = list(x = Numeric, y = Numeric), .return = Numeric ) add(1, 2) # Returns 3 try(add("a", 2)) # Error: Type error in 'x': Expected numeric, got string # Optional parameter greet <- typed_function( function(name, title = NULL) { if (is.null(title)) paste("Hello,", name) else paste("Hello,", title, name) }, params = list(name = String, title = Optional(String)) ) greet("Alice") # "Hello, Alice" greet("Alice", title = "Dr.") # "Hello, Dr. Alice" try(greet("Alice", title = 42)) # Error: Type error in 'title' # Union type in params describe <- typed_function( function(id) paste("ID:", id), params = list(id = String | Numeric), .return = String ) describe("abc") # "ID: abc" describe(123) # "ID: 123" try(describe(TRUE)) # Error: Type error in 'id'# Basic typed function add <- typed_function( function(x, y) x + y, params = list(x = Numeric, y = Numeric), .return = Numeric ) add(1, 2) # Returns 3 try(add("a", 2)) # Error: Type error in 'x': Expected numeric, got string # Optional parameter greet <- typed_function( function(name, title = NULL) { if (is.null(title)) paste("Hello,", name) else paste("Hello,", title, name) }, params = list(name = String, title = Optional(String)) ) greet("Alice") # "Hello, Alice" greet("Alice", title = "Dr.") # "Hello, Dr. Alice" try(greet("Alice", title = 42)) # Error: Type error in 'title' # Union type in params describe <- typed_function( function(id) paste("ID:", id), params = list(id = String | Numeric), .return = String ) describe("abc") # "ID: abc" describe(123) # "ID: 123" try(describe(TRUE)) # Error: Type error in 'id'
S3 methods for the '|' operator to create union types.
## S3 method for class 'sicher_type' type1 | type2 ## S3 method for class 'sicher_union' type1 | type2## S3 method for class 'sicher_type' type1 | type2 ## S3 method for class 'sicher_union' type1 | type2
type1 |
First type (sicher_type or sicher_union object) |
type2 |
Second type (sicher_type or sicher_union object) |
A union type (sicher_union object)