knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
The interface
package provides a system for defining and implementing interfaces in R, with runtime type checking. This approach brings some of the benefits of statically-typed languages to R, allowing for more structured and safer code.
Interfaces in R can be beneficial for several reasons:
interface
package provides the following:
To install the package, use the following command:
# Install the package from the source remotes::install_github("dereckmezquita/interface")
Import the package functions.
box::use(interface[interface, type.frame, fun, enum])
To define an interface, use the interface()
function:
# Define an interface Person <- interface( name = character, age = numeric, email = character ) # Implement the interface john <- Person( name = "John Doe", age = 30, email = "john@example.com" ) print(john) # interfaces are lists print(john$name) # Valid assignment john$age <- 10 print(john$age) # Invalid assignment (throws error) try(john$age <- "thirty")
You can create nested interfaces and extend existing interfaces:
# Define an Address interface Address <- interface( street = character, city = character, postal_code = character ) # Define a Scholarship interface Scholarship <- interface( amount = numeric, status = logical ) # Extend the Person and Address interfaces Student <- interface( extends = c(Address, Person), # will inherit properties from Address and Person student_id = character, scores = data.table::data.table, scholarship = Scholarship # nested interface ) # Implement the extended interface john_student <- Student( name = "John Doe", age = 30, email = "john@example.com", street = "123 Main St", city = "Small town", postal_code = "12345", student_id = "123456", scores = data.table::data.table( subject = c("Math", "Science"), score = c(95, 88) ), scholarship = Scholarship( amount = 5000, status = TRUE ) ) print(john_student)
Interfaces can have custom validation functions:
is_valid_email <- function(x) { grepl("[a-z|0-9]+\\@[a-z|0-9]+\\.[a-z|0-9]+", x) } UserProfile <- interface( username = character, email = is_valid_email, age = function(x) is.numeric(x) && x >= 18 ) # Implement with valid data valid_user <- UserProfile( username = "john_doe", email = "john@example.com", age = 25 ) print(valid_user) # Invalid implementation (throws error) try(UserProfile( username = "jane_doe", email = "not_an_email", age = "30" ))
Enums provide a way to define a set of named constants. They are useful for representing a fixed set of values and can be used in interfaces to ensure that a property only takes on one of a predefined set of values.
# Define an enum for days of the week DaysOfWeek <- enum("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") # Create an enum object today <- DaysOfWeek("Wednesday") print(today) # Valid assignment today$value <- "Friday" print(today) # Invalid assignment (throws error) try(today$value <- "NotADay")
Enums can be used as property types in interfaces:
# Define an interface using an enum Meeting <- interface( title = character, day = DaysOfWeek, start_time = numeric, duration = numeric ) # Create a valid meeting object standup <- Meeting( title = "Daily Standup", day = "Monday", start_time = 9.5, # 9:30 AM duration = 0.5 # 30 minutes ) print(standup) # Invalid day (throws error) try(Meeting( title = "Invalid Meeting", day = "InvalidDay", start_time = 10, duration = 1 ))
You can also declare enums directly within an interface definition:
# Define an interface with an in-place enum Task <- interface( description = character, priority = enum("Low", "Medium", "High"), status = enum("Todo", "InProgress", "Done") ) # Create a task object my_task <- Task( description = "Complete project report", priority = "High", status = "InProgress" ) print(my_task) # Update task status my_task$status$value <- "Done" print(my_task) # Invalid priority (throws error) try(my_task$priority$value <- "VeryHigh")
Enums provide an additional layer of type safety and clarity to your code. They ensure that certain properties can only take on a predefined set of values, reducing the chance of errors and making the code more self-documenting.
Define functions with strict type constraints:
typed_fun <- fun( x = numeric, y = numeric, return = numeric, impl = function(x, y) { return(x + y) } ) # Valid call print(typed_fun(1, 2)) # [1] 3 # Invalid call (throws error) try(typed_fun("a", 2))
typed_fun2 <- fun( x = c(numeric, character), y = numeric, return = c(numeric, character), impl = function(x, y) { if (is.numeric(x)) { return(x + y) } else { return(paste(x, y)) } } ) print(typed_fun2(1, 2)) # [1] 3 print(typed_fun2("a", 2)) # [1] "a 2"
data.frame
/data.table
sCreate data frames with column type constraints and row validation:
PersonFrame <- type.frame( frame = data.frame, col_types = list( id = integer, name = character, age = numeric, is_student = logical ) ) # Create a data frame persons <- PersonFrame( id = 1:3, name = c("Alice", "Bob", "Charlie"), age = c(25, 30, 35), is_student = c(TRUE, FALSE, TRUE) ) print(persons) # Invalid modification (throws error) try(persons$id <- letters[1:3])
PersonFrame <- type.frame( frame = data.frame, col_types = list( id = integer, name = character, age = numeric, is_student = logical, gender = enum("M", "F"), email = function(x) all(grepl("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", x)) # functions are applied to whole column ), freeze_n_cols = FALSE, row_callback = function(row) { if (row$age >= 40) { return(sprintf("Age must be less than 40 (got %d)", row$age)) } if (row$name == "Yanice") { return("Name cannot be 'Yanice'") } return(TRUE) }, allow_na = FALSE, on_violation = "error" ) df <- PersonFrame( id = 1:3, name = c("Alice", "Bob", "Charlie"), age = c(25, 35, 35), is_student = c(TRUE, FALSE, TRUE), gender = c("F", "M", "M"), email = c("alice@test.com", "bob_no_valid@test.com", "charlie@example.com") ) print(df) summary(df) # Invalid row addition (throws error) try(rbind(df, data.frame( id = 4, name = "David", age = 500, is_student = TRUE, email = "d@test.com" )))
This package provides powerful tools for ensuring type safety and validation in R. By defining interfaces, enums, typed functions, and typed data frames, you can create robust and reliable data structures and functions with strict type constraints.
This vignette demonstrates the basic usage and capabilities of the package. For more details, refer to the package documentation and examples provided in the source code.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.