Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MISC-1: R interface package; initial build and research #1

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
827ca97
Project files.
dereckmezquita Jul 1, 2024
fecaf8e
Docs for devs.
dereckmezquita Jul 1, 2024
f94e8b5
Implementing interface systems in different approaches.
dereckmezquita Jul 1, 2024
bdd0666
Developing interfaces in S4 to support more types and custom types fr…
dereckmezquita Jul 1, 2024
6a6b39a
Support any type.
dereckmezquita Jul 1, 2024
88412a3
Updated R6 implementation.
dereckmezquita Jul 1, 2024
0c6a8b3
Custom implmentation likely looks like best one, since it's simple an…
dereckmezquita Jul 1, 2024
3551fdb
Formatting.
dereckmezquita Jul 1, 2024
0b58b27
Validate on access functionality.
dereckmezquita Jul 1, 2024
808660a
Allow for validate to be optional from interface creation.
dereckmezquita Jul 1, 2024
13618d8
Don't attach validate if the user doesn't want it avoids overhead.
dereckmezquita Jul 1, 2024
18b8258
Custom print methods.
dereckmezquita Jul 1, 2024
19a16af
Basic package functions.
dereckmezquita Jul 1, 2024
6727d52
Moved to dev folder.
dereckmezquita Jul 1, 2024
15185a2
Ignore dev folders.
dereckmezquita Jul 1, 2024
bd13110
Docs.
dereckmezquita Jul 1, 2024
6ed21e0
Readme.
dereckmezquita Jul 1, 2024
92d470d
Namespace after build.
dereckmezquita Jul 1, 2024
e15b32c
Project files.
dereckmezquita Jul 1, 2024
722508e
PR template.
dereckmezquita Jul 1, 2024
69dd9e0
Workflow.
dereckmezquita Jul 1, 2024
692764a
TODO doc.
dereckmezquita Jul 1, 2024
3f03a05
Wordlist.
dereckmezquita Jul 1, 2024
742121a
Ignore workflows.
dereckmezquita Jul 1, 2024
5ccc6fb
Wordlist.
dereckmezquita Jul 1, 2024
cc18d39
Deps.
dereckmezquita Jul 1, 2024
ec3e242
Precommit.
dereckmezquita Jul 1, 2024
0a318db
Trying to get styler to work
dereckmezquita Jul 1, 2024
4ec2c77
Delete workflow for now.
dereckmezquita Jul 6, 2024
7fbd705
More robus functions.
dereckmezquita Jul 6, 2024
9db6748
Version.
dereckmezquita Jul 6, 2024
6d4a88f
Update owner.
dereckmezquita Jul 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
^.*\.Rproj$
^\.Rproj\.user$
^\.pre-commit-config\.yaml$
^renv$
^renv\.lock$
dev-docs/
dev/
research/
4 changes: 4 additions & 0 deletions .Rprofile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source("renv/activate.R")
options(
styler.addins_style_transformer = "styler::tidyverse_style(indent_by = 4L)"
)
7 changes: 7 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# jira ticket

https://dereckmezquita.atlassian.net/browse/<TICKET-ID>

# todo list

- [ ] Version bump
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# renv
/renv/
!renv.lock

# History files
.Rhistory
.Rapp.history
Expand Down
6 changes: 6 additions & 0 deletions .lintr
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
linters: linters_with_defaults(
object_name_linter = NULL,
indentation_linter = indentation_linter(indent = 4),
line_length_linter = line_length_linter(120),
commented_code_linter = NULL
)
80 changes: 80 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# All available hooks: https://pre-commit.com/hooks.html
# R specific hooks: https://github.com/lorenzwalthert/precommit
repos:
- repo: https://github.com/lorenzwalthert/precommit
rev: v0.4.2
hooks:
- id: style-files
args: [--style_pkg=styler, --style_fun=tidyverse_style, --indent_by=4]
- id: roxygenize
# codemeta must be above use-tidy-description when both are used
# - id: codemeta-description-updated
- id: use-tidy-description
- id: spell-check
exclude: >
(?x)^(
.*\.[rR]|
.*\.feather|
.*\.jpeg|
.*\.pdf|
.*\.png|
.*\.py|
.*\.RData|
.*\.rds|
.*\.Rds|
.*\.Rproj|
.*\.sh|
(.*/|)\.gitignore|
(.*/|)\.gitlab-ci\.yml|
(.*/|)\.lintr|
(.*/|)\.pre-commit-.*|
(.*/|)\.Rbuildignore|
(.*/|)\.Renviron|
(.*/|)\.Rprofile|
(.*/|)\.travis\.yml|
(.*/|)appveyor\.yml|
(.*/|)NAMESPACE|
(.*/|)renv/settings\.dcf|
(.*/|)renv\.lock|
(.*/|)WORDLIST|
\.github/workflows/.*|
data/.*|
)$
- id: lintr
args: [--warn_only]
exclude: dev/.*|
- id: readme-rmd-rendered
- id: parsable-R
- id: no-browser-statement
- id: no-print-statement
exclude: dev/.*|
- id: no-debug-statement
- id: deps-in-desc
args: [--allow_private_imports]
- id: pkgdown
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-added-large-files
args: ['--maxkb=200']
- id: file-contents-sorter
files: '^\.Rbuildignore$'
- id: end-of-file-fixer
exclude: '\.Rd'
- repo: https://github.com/pre-commit-ci/pre-commit-ci-config
rev: v1.6.1
hooks:
# Only required when https://pre-commit.ci is used for config validation
- id: check-pre-commit-ci-config
- repo: local
hooks:
- id: forbid-to-commit
name: Don't commit common R artifacts
entry: Cannot commit .Rhistory, .RData, .Rds or .rds.
language: fail
files: '\.(Rhistory|RData|Rds|rds)$'
# `exclude: <regex>` to allow committing specific files

ci:
autoupdate_schedule: monthly
skip: [pkgdown]
22 changes: 22 additions & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Package: interface
Type: Package
Title: Interfaces for data validation and typing in R
Version: 0.0.1
Authors@R:
person(given = "Dereck",
family = "Mezquita",
role = c("aut", "cre"),
email = "[email protected]",
comment = c(ORCID = "0000-0002-9307-6762"))
Maintainer: Dereck Mezquita <[email protected]>
Description: An easy to use TypeScript-like system for defining and implementing interfaces in R, with optional runtime type checking.
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
VignetteBuilder: knitr
RoxygenNote: 7.3.2
Suggests:
testthat (>= 3.0.0),
knitr,
rmarkdown,
box
8 changes: 8 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Generated by roxygen2: do not edit by hand

S3method("$",validated_list)
S3method(print,Interface)
S3method(print,InterfaceImplementation)
S3method(summary,Interface)
export(implement)
export(interface)
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# interface 0.1.0

- Initial CRAN submission.
- Implemented core functionality for defining and implementing interfaces.
- Added support for optional runtime type checking.
- Included support for nested interfaces and custom validation functions.
- Created comprehensive documentation and vignettes.
57 changes: 57 additions & 0 deletions R/helpers.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Helper function to check if a value matches a type specification
check_type <- function(value, type_spec) {
if (identical(type_spec, "ANY")) {
return(TRUE)
} else if (inherits(type_spec, "Interface")) {
return(check_interface(value, type_spec))
} else if (is.character(type_spec)) {
if (type_spec %in% c("numeric", "integer", "logical", "character", "list")) {
return(inherits(value, type_spec))
} else {
stop(sprintf("Unsupported character type specification: %s", type_spec))
}
} else if (is.function(type_spec)) {
tryCatch(
{
result <- type_spec(value)
if (!is.logical(result) || length(result) != 1) {
stop("Custom type check function must return a single logical value")
}
return(result)
},
error = function(e) {
stop(sprintf("Error in custom type check function: %s", e$message))
}
)
} else {
stop(sprintf("Unsupported type specification: %s", class(type_spec)[1]))
}
}

# Helper function to check if a value implements an interface
check_interface <- function(value, interface) {
if (!is.list(value)) {
return(FALSE)
}
all(names(interface$properties) %in% names(value)) &&
all(mapply(check_type, value[names(interface$properties)], interface$properties))
}

# Validation function
validate_object <- function(obj, interface) {
for (prop in names(interface$properties)) {
expected_type <- interface$properties[[prop]]
actual_value <- obj[[prop]]

if (!check_type(actual_value, expected_type)) {
stop(sprintf("Property '%s' does not match the expected type specification", prop))
}
}
return(TRUE)
}

# Custom accessor function
custom_accessor <- function(x, i) {
validate_object(x, attr(x, "interface"))
x[[i]]
}
58 changes: 58 additions & 0 deletions R/implement.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#' Implement an Interface
#'
#' @param interface An Interface object
#' @param ... Properties to implement the interface
#' @param validate_on_access Logical, whether to validate on access
#'
#' @return An object implementing the interface
#' @export
implement <- function(interface, ..., validate_on_access = NULL, allow_extra = FALSE) {
obj <- list(...)

# Check if all required properties are present and not NULL
missing_props <- setdiff(names(interface$properties), names(obj))
null_props <- names(obj)[vapply(obj, is.null, logical(1))]
if (length(missing_props) > 0 || length(null_props) > 0) {
stop(paste(
"Missing or NULL properties:",
paste(c(missing_props, null_props), collapse = ", ")
))
}

# Remove extra properties if not allowed
if (!allow_extra) {
extra_props <- setdiff(names(obj), names(interface$properties))
if (length(extra_props) > 0) {
obj <- obj[names(interface$properties)]
warning(paste("Removed extra properties:", paste(extra_props, collapse = ", ")))
}
}

# Initial validation
validate_object(obj, interface)

# Determine validate_on_access value
if (is.null(validate_on_access)) {
validate_on_access <- interface$validate_on_access
}

# Prepare class and attributes
class_name <- paste0(interface$interface_name, "Implementation")
classes <- c(class_name, "InterfaceImplementation", "list")

if (validate_on_access) {
classes <- c("validated_list", classes)
}

# Return the object with appropriate class and attributes
return(structure(
obj,
class = classes,
interface = interface,
validate_on_access = if (validate_on_access) TRUE else NULL,
allow_extra = allow_extra
))
}

#' @export
`$.validated_list` <- custom_accessor
25 changes: 25 additions & 0 deletions R/interface.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#' Create an Interface
#'
#' @param interface_name A character string naming the interface
#' @param ... Property definitions for the interface
#' @param validate_on_access Logical, whether to validate on access by default
#'
#' @return An Interface object
#' @export
interface <- function(interface_name, ..., validate_on_access = FALSE) {
properties <- list(...)
structure(list(
interface_name = interface_name,
properties = properties,
validate_on_access = validate_on_access
), class = "Interface")
}

# Internal function, no need to export
Interface <- function(interface_name, properties, validate_on_access = FALSE) {
structure(list(
interface_name = interface_name,
properties = properties,
validate_on_access = validate_on_access
), class = "Interface")
}
47 changes: 47 additions & 0 deletions R/print_methods.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#' @export
print.InterfaceImplementation <- function(x, ...) {
interface <- attr(x, "interface")
cat("Object implementing", interface$interface_name, "interface:\n")
for (prop in names(x)) {
cat(sprintf(" %s: ", prop))
if (is.atomic(x[[prop]]) && length(x[[prop]]) == 1) {
cat(x[[prop]], "\n")
} else if (inherits(x[[prop]], "InterfaceImplementation")) {
cat("<", class(x[[prop]])[1], ">\n", sep = "")
} else {
cat("<", class(x[[prop]])[1], ">\n", sep = "")
}
}
cat(
"Validation on access:",
if (isTRUE(attr(x, "validate_on_access"))) "Enabled" else "Disabled",
"\n"
)
invisible(x)
}

#' @export
print.Interface <- function(x, ...) {
cat("Interface:", x$interface_name, "\n")
cat("Properties:\n")
for (prop in names(x$properties)) {
prop_type <- x$properties[[prop]]
if (inherits(prop_type, "Interface")) {
cat(sprintf(" %s: <Interface %s>\n", prop, prop_type$interface_name))
} else if (is.function(prop_type)) {
cat(sprintf(" %s: <Custom Validator>\n", prop))
} else {
cat(sprintf(" %s: %s\n", prop, prop_type))
}
}
cat("Default validation on access:", if (x$validate_on_access) "Enabled" else "Disabled", "\n")
invisible(x)
}

#' @export
summary.Interface <- function(object, ...) {
cat("Interface:", object$interface_name, "\n")
cat("Number of properties:", length(object$properties), "\n")
cat("Default validation on access:", if (object$validate_on_access) "Enabled" else "Disabled", "\n")
invisible(object)
}
Loading