# listenv: Environments Behaving (Almost) as Lists ## Summary *List environments* are environments that have list-like properties. They are implemented by the [listenv](https://cran.r-project.org/package=listenv) package. The main features of a list environment are summarized in the below table: | Property | list environments | lists | environments | |----|:--:|:--:|:--:| | Number of elements, e.g. [`length()`](https://rdrr.io/r/base/length.html) | yes | yes | yes | | Named elements, e.g. [`names()`](https://rdrr.io/r/base/names.html), `x$a` and `x[["a"]]` | yes | yes | yes | | Duplicated names | yes | yes | | | Element names are optional | yes | yes | | | Indexed elements, e.g. `x[[4]]` | yes | yes | | | Dimensions, e.g. `dim(x)`, `t(x)`, and `aperm(x, c(3,1,2))` | yes | yes | | | Names of dimensions, e.g. `dimnames(x)` | yes | yes | | | Indexing by dimensions, e.g. `x[[2, 4]]` and `x[[2, "D"]]` | yes | yes | | | Multi-element subsetting, e.g. `x[c("a", "c")]`, `x[-1]` and `x[2:1, , 3]` | yes | yes | | | Multi-element subsetting preserves element names | yes | | | | Resizing, e.g. `length(x) <- 6` | yes | yes | | | Removing elements by assigning NULL, e.g. `x$c <- NULL` and `x[1:3] <- NULL` | yes | yes | | | Removing parts of dimensions by assigning NULL, e.g. `x[,2] <- NULL` | yes | | | | Mutable, e.g. `y <- x; y$a <- 3; identical(y, x)` | yes | | yes | | Compatible\* with [`assign()`](https://rdrr.io/r/base/assign.html), [`delayedAssign()`](https://rdrr.io/r/base/delayedAssign.html), [`get()`](https://rdrr.io/r/base/get.html) and [`exists()`](https://rdrr.io/r/base/exists.html) | yes | | yes | For example, ``` r > x <- listenv(a = 1, b = 2, c = "hello") > x A ‘listenv’ vector with 3 elements (‘a’, ‘b’, ‘c’). > length(x) [1] 3 > names(x) [1] "a" "b" "c" > x$a [1] 1 > x[[3]] <- toupper(x[[3]]) > x$c [1] "HELLO" > y <- x > y$d <- y$a + y[["b"]] > names(y)[2] <- "a" > y$a [1] 1 > y A ‘listenv’ vector with 4 elements (‘a’, ‘a’, ‘c’, ‘d’). > identical(y, x) [1] TRUE > for (ii in seq_along(x)) { + cat(sprintf("Element %d (%s): %s\n", ii, sQuote(names(x)[ii]), + x[[ii]])) + } Element 1 (‘a’): 1 Element 2 (‘a’): 2 Element 3 (‘c’): HELLO Element 4 (‘d’): 3 > x[c(1, 3)] <- list(2, "Hello world!") > x A ‘listenv’ vector with 4 elements (‘a’, ‘a’, ‘c’, ‘d’). > y <- as.list(x) > str(y) List of 4 $ a: num 2 $ a: num 2 $ c: chr "Hello world!" $ d: num 3 > z <- as.listenv(y) > z A ‘listenv’ vector with 4 elements (‘a’, ‘a’, ‘c’, ‘d’). > identical(z, x) [1] FALSE > all.equal(z, x) [1] TRUE ``` ## Creating list environments List environments are created similarly to lists but also similarly to environments. To create an empty list environment, use ``` r > x <- listenv() > x A ‘listenv’ vector with 0 elements (unnamed). ``` This can later be populated using named assignments, ``` r > x$a <- 1 > x A ‘listenv’ vector with 1 element (‘a’). ``` comparable to how both lists and environments work. Similarly to lists, they can also be populated using indices, e.g. ``` r > x[[2]] <- 2 > x$c <- 3 > x A ‘listenv’ vector with 3 elements (‘a’, ‘’, ‘c’). ``` Just as for lists, a list environment is expanded with `NULL` elements whenever a new element is added that is beyond the current length plus one, e.g. ``` r > x[[5]] <- 5 > x A ‘listenv’ vector with 5 elements (‘a’, ‘’, ‘c’, ‘’, ‘’). > x[[4]] NULL ``` As with lists, the above list environment can also be created from the start, e.g. ``` r > x <- listenv(a = 1, 3, c = 4, NULL, 5) > x A ‘listenv’ vector with 5 elements (‘a’, ‘’, ‘c’, ‘’, ‘’). ``` As for lists, the length of a list environment can at any time be increased or decreased by assigning it a new length. If decreased, elements are dropped, e.g. ``` r > x A ‘listenv’ vector with 5 elements (‘a’, ‘’, ‘c’, ‘’, ‘’). > length(x) <- 2 > x A ‘listenv’ vector with 2 elements (‘a’, ‘’). > x[[1]] [1] 1 > x[[2]] [1] 3 ``` If increased, new elements are populated with unnamed elements of `NULL`, e.g. ``` r > length(x) <- 4 > x A ‘listenv’ vector with 4 elements (‘a’, ‘’, ‘’, ‘’). > x[[3]] NULL > x[[4]] NULL ``` To allocate an “empty” list environment (with all `NULL`:s) of a given length, do ``` r > x <- listenv() > length(x) <- 4 > x A ‘listenv’ vector with 4 elements (unnamed). ``` *Note*: Unfortunately, it is *not* possible to use `x <- vector("listenv", length = 4)`; that construct is only supported for the basic data types. Elements can be dropped by assigning `NULL`, e.g. to drop the first and third element of a list environment, do: ``` r > x[c(1, 3)] <- NULL > x A ‘listenv’ vector with 2 elements (unnamed). ``` ## Iterating over elements ### Iterating over elements by names Analogously to lists and plain environments, it is possible to iterate over elements of list environments by the element names. For example, ``` r > x <- listenv(a = 1, b = 2, c = 3) > for (name in names(x)) { + cat(sprintf("Element %s: %s\n", sQuote(name), x[[name]])) + } Element ‘a’: 1 Element ‘b’: 2 Element ‘c’: 3 ``` ### Iterating over elements by indices Analogously to lists, but contrary to plain environments, it is also possible to iterate over elements by their indices. For example, ``` r > x <- listenv(a = 1, b = 2, c = 3) > for (ii in seq_along(x)) { + cat(sprintf("Element %d: %s\n", ii, x[[ii]])) + } Element 1: 1 Element 2: 2 Element 3: 3 ``` ## Coercion to and from list environments ### Coercing to lists and vectors Coercing a list environment to a list: ``` r > x <- listenv(a = 2, b = 3, c = "hello") > x A ‘listenv’ vector with 3 elements (‘a’, ‘b’, ‘c’). > y <- as.list(x) > str(y) List of 3 $ a: num 2 $ b: num 3 $ c: chr "hello" ``` Coercing a list to a list environment: ``` r > z <- as.listenv(y) > z A ‘listenv’ vector with 3 elements (‘a’, ‘b’, ‘c’). > identical(z, x) [1] FALSE > all.equal(z, x) [1] TRUE ``` Unlisting: ``` r > unlist(x) a b c "2" "3" "hello" > unlist(x[-3]) a b 2 3 > unlist(x[1:2], use.names = FALSE) [1] 2 3 ``` ## Multi-dimensional list environments Analogously to lists, and contrary to plain environments, list environments can have dimensions with corresponding names. For example, ``` r > x <- as.listenv(1:6) > dim(x) <- c(2, 3) > dimnames(x) <- list(c("a", "b"), c("A", "B", "C")) > x A ‘listenv’ matrix with 6 elements (unnamed) arranged in 2x3 rows (‘a’, ‘b’) and columns (‘A’, ‘B’, ‘C’). ``` An easy way to quickly get an overview is to coerce to a list, e.g. ``` r > as.list(x) A B C a 1 3 5 b 2 4 6 ``` Individual elements of a list environment can be accessed using standard subsetting syntax, e.g. ``` r > x[["a", "B"]] [1] 3 > x[[1, 2]] [1] 3 > x[[1, "B"]] [1] 3 ``` We can assign individual elements similarly, e.g. ``` r > x[["b", "B"]] <- -x[["b", "B"]] > as.list(x) A B C a 1 3 5 b 2 -4 6 ``` We can also assign multiple elements through dimensional subsetting, e.g. ``` r > x[2, -1] <- 98:99 > as.list(x) A B C a 1 3 5 b 2 98 99 > x["a", c(1, 3)] <- list(97, "foo") > as.list(x) A B C a 97 3 "foo" b 2 98 99 > x[] <- 1:6 > as.list(x) A B C a 1 3 5 b 2 4 6 ``` Concurrently with dimensional names it is possible to have names of the individual elements just as for list environments without dimensions. For example, ``` r > names(x) <- letters[seq_along(x)] > x A ‘listenv’ matrix with 6 elements (‘a’, ‘b’, ‘c’, ..., ‘f’) arranged in 2x3 rows (‘a’, ‘b’) and columns (‘A’, ‘B’, ‘C’). > x[["a"]] [1] 1 > x[["f"]] [1] 6 > x[c("a", "f")] A ‘listenv’ vector with 2 elements (‘a’, ‘f’). > unlist(x) a b c d e f 1 2 3 4 5 6 > as.list(x) A B C a 1 3 5 b 2 4 6 attr(,"names") [1] "a" "b" "c" "d" "e" "f" ``` Contrary to lists, element names are preserved also with multi-dimensional subsetting, e.g. ``` r > x[1, 2] A ‘listenv’ vector with 1 element (‘c’). > x[1, 2, drop = FALSE] A ‘listenv’ matrix with 1 element (‘c’) arranged in 1x1 rows (‘a’) and columns (‘B’). > x[1:2, 2:1] A ‘listenv’ matrix with 4 elements (‘c’, ‘d’, ‘a’, ‘b’) arranged in 2x2 rows (‘a’, ‘b’) and columns (‘B’, ‘A’). > x[2, ] A ‘listenv’ vector with 3 elements (‘b’, ‘d’, ‘f’). > x[2, , drop = FALSE] A ‘listenv’ matrix with 3 elements (‘b’, ‘d’, ‘f’) arranged in 1x3 rows (‘b’) and columns (‘A’, ‘B’, ‘C’). > x["b", -2, drop = FALSE] A ‘listenv’ matrix with 2 elements (‘b’, ‘f’) arranged in 1x2 rows (‘b’) and columns (‘A’, ‘C’). ``` Note, whenever dimensions are set using `dim(x) <- dims` both the dimensional names and the element names are removed, e.g. ``` r > dim(x) <- NULL > names(x) NULL ``` This behavior is by design, cf. [`help("dim", package="base")`](https://rdrr.io/r/base/dim.html). To allocate an “empty” list environment array (with all `NULL`:s) of a given dimension, do ``` r > x <- listenv() > dim(x) <- c(2, 3) > dimnames(x) <- list(c("a", "b"), c("A", "B", "C")) > x A ‘listenv’ matrix with 6 elements (unnamed) arranged in 2x3 rows (‘a’, ‘b’) and columns (‘A’, ‘B’, ‘C’). ``` Rows and columns can be dropped by assigning `NULL`, e.g. to drop the first and third column of a list-environment matrix, do: ``` r > x[, c(1, 3)] <- NULL > x A ‘listenv’ matrix with 2 elements (unnamed) arranged in 2x1 rows (‘a’, ‘b’) and columns (‘B’). ``` ## Important about environments List environments are as their name suggests *environments*. Whenever working with environments in R, it is important to understand that *environments are mutable* whereas all other of the basic data types in R are immutable. For example, consider the following function that assigns zero to element `a` of object `x`: ``` r > setA <- function(x) { + x$a <- 0 + x + } ``` If we pass a regular list to this function, ``` r > x <- list(a = 1) > y <- setA(x) > x$a [1] 1 > y$a [1] 0 ``` we see that `x` is unaffected by the assignment. This is because *lists are immutable* in R. However, if we pass an environment instead, ``` r > x <- new.env() > x$a <- 1 > y <- setA(x) > x$a [1] 0 > y$a [1] 0 ``` we find that `x` was affected by the assignment. This is because *environments are mutable* in R. Since list environments inherit from environments, this also goes for them, e.g. ``` r > x <- listenv(a = 1) > y <- setA(x) > x$a [1] 0 > y$a [1] 0 ``` What is also important to understand is that it is not just the *content* of an environment that is mutable but also its *attributes*. For example, ``` r > x <- listenv(a = 1) > y <- x > attr(y, "foo") <- "Hello!" > attr(x, "foo") [1] "Hello!" ``` More importantly, since dimensions and their names are also attributes, this also means they are mutable. For example, ``` r > x <- as.listenv(1:6) > dim(x) <- c(2, 3) > x A ‘listenv’ matrix with 6 elements (unnamed) arranged in 2x3 unnamed rows and columns. > y <- x > dim(y) <- c(3, 2) > x A ‘listenv’ matrix with 6 elements (unnamed) arranged in 3x2 unnamed rows and columns. ``` ## Installation R package listenv is available on [CRAN](https://cran.r-project.org/package=listenv) and can be installed in R as: ``` r install.packages("listenv") ``` ### Pre-release version To install the pre-release version that is available in Git branch `develop` on GitHub, use: ``` r remotes::install_github("futureverse/listenv", ref="develop") ``` This will install the package from source. # Package index ## All functions - [`aperm(`*``*`)`](https://listenv.futureverse.org/reference/aperm.listenv.md) [`t(`*``*`)`](https://listenv.futureverse.org/reference/aperm.listenv.md) : Transpose a 'listenv' array by permuting its dimensions - [`` `dim_na<-`() ``](https://listenv.futureverse.org/reference/dim_na.md) : Set the dimension of an object - [`listenv()`](https://listenv.futureverse.org/reference/listenv.md) [`as.listenv()`](https://listenv.futureverse.org/reference/listenv.md) : Create a list environment # Articles ### All vignettes - [List Environments](https://listenv.futureverse.org/articles/listenv.md):