# Introduction to interface

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.

## Why Use Interfaces?

Interfaces in R can be beneficial for several reasons:

1. Code Structure: They provide a clear contract for what properties and methods an object should have.
2. Type Safety: They allow for runtime type checking, catching errors early.
3. Documentation: They serve as self-documenting code, clearly stating the expected structure of objects.
4. Flexibility: They allow for implementation of multiple interfaces, promoting code reuse.

`interface` package provides the following:

1. Interfaces: Define and implement interfaces with type checking. Interfaces can be extended and nested.
2. Typed Functions: Define functions with strict type constraints.
3. Typed Data Frames: Create data frames with column type constraints and row validation. Data frames can have custom validation functions and row callbacks.
4. Enums: Define and use enumerated types for stricter type safety.

## Installation

To install the package, use the following command:

    # Install the package from the source
    remotes::install_github("dereckmezquita/interface")

## Usage

Import the package functions.

    box::use(interface[interface, type.frame, fun, enum])

### Interfaces

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)
    #> Object implementing interface:
    #>   name: John Doe
    #>   age: 30
    #>   email: john@example.com
    #> Validation on access: Disabled

    # interfaces are lists
    print(john$name)
    #> [1] "John Doe"

    # Valid assignment
    john$age <- 10

    print(john$age)
    #> [1] 10

    # Invalid assignment (throws error)
    try(john$age <- "thirty")
    #> Error : Property 'age' must be of type numeric

### Nested Interfaces and Extending Interfaces

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)
    #> Object implementing interface:
    #>   student_id: 123456
    #>   scores: Math
    #>    scores: Science
    #>    scores: 95
    #>    scores: 88
    #>   scholarship: <environment: 0x120ae8700>
    #>   street: 123 Main St
    #>   city: Small town
    #>   postal_code: 12345
    #>   name: John Doe
    #>   age: 30
    #>   email: john@example.com
    #> Validation on access: Disabled

### Custom Validation Functions

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)
    #> Object implementing interface:
    #>   username: john_doe
    #>   email: john@example.com
    #>   age: 25
    #> Validation on access: Disabled

    # Invalid implementation (throws error)
    try(UserProfile(
        username = "jane_doe",
        email = "not_an_email",
        age = "30"
    ))
    #> Error : Errors occurred during interface creation:
    #>   - Invalid value for property 'email': FALSE
    #>   - Invalid value for property 'age': FALSE

### Enums

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.

#### Creating and Using Enums

    # 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)
    #> Enum: Wednesday

    # Valid assignment
    today$value <- "Friday"
    print(today)
    #> Enum: Friday

    # Invalid assignment (throws error)
    try(today$value <- "NotADay")
    #> Error in `$<-.enum`(`*tmp*`, value, value = "NotADay") :
    #>   Invalid value. Must be one of: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday

#### Using Enums in Interfaces

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)
    #> Object implementing interface:
    #>   title: Daily Standup
    #>   day: Monday
    #>   start_time: 9.5
    #>   duration: 0.5
    #> Validation on access: Disabled

    # Invalid day (throws error)
    try(Meeting(
        title = "Invalid Meeting",
        day = "InvalidDay",
        start_time = 10,
        duration = 1
    ))
    #> Error : Errors occurred during interface creation:
    #>   - Invalid enum value for property 'day': Invalid value. Must be one of: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday

#### In-place Enum Declaration in Interfaces

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)
    #> Object implementing interface:
    #>   description: Complete project report
    #>   priority: High
    #>   status: InProgress
    #> Validation on access: Disabled

    # Update task status
    my_task$status$value <- "Done"
    print(my_task)
    #> Object implementing interface:
    #>   description: Complete project report
    #>   priority: High
    #>   status: Done
    #> Validation on access: Disabled

    # Invalid priority (throws error)
    try(my_task$priority$value <- "VeryHigh")
    #> Error in `$<-.enum`(`*tmp*`, value, value = "VeryHigh") :
    #>   Invalid value. Must be one of: Low, Medium, High

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.

### Typed Functions

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
    #> [1] 3

    # Invalid call (throws error)
    try(typed_fun("a", 2))
    #> Error : Property 'x' must be of type numeric

#### Functions with Multiple Possible Return Types

    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
    #> [1] 3
    print(typed_fun2("a", 2))  # [1] "a 2"
    #> [1] "a 2"

### Typed `data.frame`/`data.table`s

Create 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)
    #> Typed Data Frame Summary:
    #> Base Frame Type: data.frame
    #> Dimensions: 3 rows x 4 columns
    #>
    #> Column Specifications:
    #>   id         : integer
    #>   name       : character
    #>   age        : numeric
    #>   is_student : logical
    #>
    #> Frame Properties:
    #>   Freeze columns : Yes
    #>   Allow NA       : Yes
    #>   On violation   : error
    #>
    #> Data Preview:
    #>   id    name age is_student
    #> 1  1   Alice  25       TRUE
    #> 2  2     Bob  30      FALSE
    #> 3  3 Charlie  35       TRUE

    # Invalid modification (throws error)
    try(persons$id <- letters[1:3])
    #> Error : Property 'id' must be of type integer

#### Additional Options

    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)
    #> Typed Data Frame Summary:
    #> Base Frame Type: data.frame
    #> Dimensions: 3 rows x 6 columns
    #>
    #> Column Specifications:
    #>   id         : integer
    #>   name       : character
    #>   age        : numeric
    #>   is_student : logical
    #>   gender     : Enum(M, F)
    #>   email      : custom function
    #>
    #> Frame Properties:
    #>   Freeze columns : No
    #>   Allow NA       : No
    #>   On violation   : error
    #>
    #> Data Preview:
    #>   id name age is_student gender email
    #> 1  1 TRUE   1       TRUE   TRUE  TRUE
    #> 2  1 TRUE   1       TRUE   TRUE  TRUE
    #> 3  1 TRUE   1       TRUE   TRUE  TRUE
    summary(df)
    #>        id        name                age    is_student
    #>  Min.   :1   Length:3           Min.   :1   Mode:logical
    #>  1st Qu.:1   Class :character   1st Qu.:1   TRUE:3
    #>  Median :1   Mode  :character   Median :1
    #>  Mean   :1                      Mean   :1
    #>  3rd Qu.:1                      3rd Qu.:1
    #>  Max.   :1                      Max.   :1
    #>  gender.Length  gender.Class  gender.Mode    email
    #>  1        -none-   logical                Length:3
    #>  1        -none-   logical                Class :character
    #>  1        -none-   logical                Mode  :character
    #>
    #>
    #>

    # Invalid row addition (throws error)
    try(rbind(df, data.frame(
        id = 4,
        name = "David",
        age = 500,
        is_student = TRUE,
        email = "d@test.com"
    )))
    #> Error in rbind(deparse.level, ...) : Number of columns must match

## Conclusion

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.