Last active
April 3, 2024 03:35
-
-
Save Yunuuuu/f5450d1d58e680cc4ba79dafcce636ae to your computer and use it in GitHub Desktop.
Use environment to define internal namespace and sub-namespace like Python. Instead of using `R6` object, which will make the data object mutable, this will break normal scoping rules of R.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This is not same with `r-polars` pacakge, which use more complicated design to create namespace and sub-namespace | |
| # | |
| # In package namespace ----------------------- | |
| utils::globalVariables("self") | |
| # Check that all elements of a list are named. | |
| # NULL and empty lists return TRUE. | |
| all_named <- function(x) { | |
| if (length(names(x)) != length(x) || any(names(x) == "")) { | |
| return(FALSE) | |
| } | |
| TRUE | |
| } | |
| # Check that all elements of a list are functions | |
| all_functions <- function(x) all(vapply(x, is.function, logical(1L))) | |
| # create New namespace, which is just an environment ------------ | |
| # 1. For every new namespace, we must add a new S3 method `$.ClassName` and | |
| # `[[.ClassName` to bind the namespace into the special class | |
| # (dispatch_method). | |
| # 2. We can use `self` to refer the input data (other methods or fields can | |
| # be directly acquired by `self$`) | |
| # 3. For a sub-namespace, we must define an active function to change the | |
| # underlying class of the data value in the parent namepace. Then add a new | |
| # S3 method `$.ClassName` and `[[.ClassName` to bind the sub-namespace into | |
| # the new class | |
| # 4. we can also check the data value match a special data type for the | |
| # sub-namespace | |
| new_namespace <- function(public = list(), active = list()) { | |
| if (!all_named(public) || !all_named(active)) { | |
| stop("All elements of public, and active must be named.") | |
| } | |
| allnames <- c(names(public), names(active)) | |
| if (anyDuplicated(allnames)) { | |
| stop("All items in public, and active must have unique names.") | |
| } | |
| if (any("self" == allnames)) { | |
| stop("Items cannot use reserved names 'self'.") | |
| } | |
| if (!all_functions(public)) { | |
| stop("All items in public must be functions.") | |
| } | |
| if (!all_functions(active)) { | |
| stop("All items in active must be functions.") | |
| } | |
| namespace <- new.env(parent = emptyenv()) | |
| namespace$public <- new.env(parent = emptyenv()) | |
| namespace$active <- new.env(parent = emptyenv()) | |
| list2env(public, namespace$public) | |
| list2env(active, namespace$active) | |
| namespace | |
| } | |
| # function used to dispatch methods defined in `namespace` to a special class | |
| # function used to create `$.ClassName` and `[[.ClassName` method | |
| dispatch_method <- function(.__class__, .__namespace__) { | |
| # we use special name to prevent override other function (also known as private methods) defined this package | |
| force(.__class__) | |
| force(.__namespace__) | |
| function(self, .__name__) { | |
| # environment used to find `self`, `self` should be the data value | |
| # the parent environment of this environment is the package namespace | |
| # it's safe to just change the function environment in this package | |
| force(self) | |
| .__enclos_env__ <- environment() | |
| .__public_env__ <- .subset2(.__namespace__, "public") | |
| .__active_env__ <- .subset2(.__namespace__, "active") | |
| if (exists(.__name__, envir = .__public_env__, inherits = FALSE)) { | |
| .__fn__ <- .subset2(.__public_env__, .__name__) | |
| environment(.__fn__) <- .__enclos_env__ | |
| .__fn__ | |
| } else if ( | |
| exists(.__name__, envir = .__active_env__, inherits = FALSE)) { | |
| .__fn__ <- .subset2(.__active_env__, .__name__) | |
| environment(.__fn__) <- .__enclos_env__ | |
| makeActiveBinding(".__active_fn__", .__fn__, .__enclos_env__) | |
| .__active_fn__ # nolint | |
| } else { | |
| stop(sprintf( | |
| "No method `%s()` found for Class `%s`", .__name__, .__class__ | |
| )) | |
| } | |
| } | |
| } | |
| # Let's do some examples ---------------------------- | |
| SeriesNameSpace <- new_namespace( | |
| public = list( # Methods used by user | |
| slice = function(offset, length = NULL) { # 0-based index | |
| if (is.null(length)) end <- self$len else end <- offset + length | |
| self[offset:end] | |
| } | |
| ), | |
| active = list( # fields (data property value) should be an active binding | |
| name = function() { | |
| attr(self, "name") | |
| }, | |
| len = function() { | |
| length(self) | |
| }, | |
| dtype = function() { | |
| typeof(self) | |
| }, | |
| struct = function() { | |
| if (!all_named(self)) { | |
| stop("data must be a `struct` data") # I suppose `struct` data is just a named series | |
| } | |
| class(self) <- "Series_Struct" | |
| invisible(self) | |
| } | |
| ) | |
| ) | |
| `$.Series` <- dispatch_method("Series", SeriesNameSpace) | |
| `[[.Series` <- `$.Series` | |
| # add a function to create the Series object | |
| new_series <- function(value, name = "", ...) { | |
| structure(value, name = name, ..., class = "Series") | |
| } | |
| # For a sub-namespace, we just change the underlying class of the data value in | |
| # the parent-namespace active field. | |
| # Then add a new S3 method `$.ClassName` and `[[.ClassName` to bind the | |
| # sub-namespace into the new class | |
| StructNameSpace <- new_namespace( | |
| public = list(), | |
| active = list( | |
| fields = function() names(self) | |
| ) | |
| ) | |
| # It's better to add a new `print` method for the sub-namespace class, | |
| # It's more useful to show the methods defined in this Namespace | |
| # Here, we just print the namespace environment | |
| print.Series_Struct <- function(x) { | |
| print(StructNameSpace) | |
| invisible(x) | |
| } | |
| `$.Series_Struct` <- dispatch_method("Series_Struct", StructNameSpace) | |
| `[[.Series_Struct` <- `$.Series_Struct` | |
| simple_series <- new_series(1:10) | |
| simple_series$len | |
| simple_series$dtype | |
| simple_series$name | |
| simple_series$slice(6L) | |
| simple_series$slice(0) | |
| # should be an error, as `simple_series` is not `struct` object | |
| simple_series$struct | |
| struct_series <- new_series(1:2, names = letters[1:2]) | |
| struct_series$struct | |
| struct_series$struct$fields |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment