####
13 KiB

# 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.