listenv: Environments Behaving (Almost) as Lists
Summary
List environments are environments that have list-like properties. They are implemented by the 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()
|
yes | yes | yes |
Named elements, e.g. names() , 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() , delayedAssign() , get() and exists()
|
yes | yes |
For example,
> 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
> x <- listenv()
> x
A βlistenvβ vector with 0 elements (unnamed).
This can later can be populated using named assignments,
> 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.
> 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.
> 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.
> 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.
> 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.
> 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
> 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:
> 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,
> 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,
> 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:
> 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:
> 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:
> 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,
> 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.
> 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.
> x[["a", "B"]]
[1] 3
> x[[1, 2]]
[1] 3
> x[[1, "B"]]
[1] 3
We can assign individual elements similarly, e.g.
> 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.
> 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,
> 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.
> 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.
> dim(x) <- NULL
> names(x)
NULL
This behavior is by design, cf. help("dim", package="base")
.
To allocate an "empty" list environment array (with all NULL
:s) of a given dimension, do
> 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:
> 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
:
> setA <- function(x) {
+ x$a <- 0
+ x
+ }
If we pass a regular list to this function,
> 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,
> 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 inherits from environments, this also goes for them, e.g.
> 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,
> 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,
> 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 and can be installed in R as:
install.packages("listenv")
Pre-release version
To install the pre-release version that is available in Git branch develop
on GitHub, use:
remotes::install_github("HenrikBengtsson/listenv", ref="develop")
This will install the package from source.
Contributing
To contribute to this package, please see CONTRIBUTING.md.