[med-svn] [r-cran-listenv] 01/02: Imported Upstream version 0.6.0

Michael Crusoe misterc-guest at moszumanska.debian.org
Sat Jun 25 23:50:18 UTC 2016


This is an automated email from the git hooks/post-receive script.

misterc-guest pushed a commit to branch master
in repository r-cran-listenv.

commit c6f21a0ffcd603259935be97b2e29c565233eed7
Author: Michael R. Crusoe <crusoe at ucdavis.edu>
Date:   Sat Jun 25 16:14:33 2016 -0700

    Imported Upstream version 0.6.0
---
 DESCRIPTION                         |  20 +
 MD5                                 |  31 ++
 NAMESPACE                           |  33 ++
 NEWS                                |  65 +++
 R/get_variable.R                    |  87 ++++
 R/listenv.R                         | 960 ++++++++++++++++++++++++++++++++++++
 R/parse_env_subset.R                | 269 ++++++++++
 R/undim.R                           |  34 ++
 R/utils.R                           |  33 ++
 build/vignette.rds                  | Bin 0 -> 230 bytes
 inst/doc/listenv.html               | 642 ++++++++++++++++++++++++
 inst/doc/listenv.md.rsp             | 360 ++++++++++++++
 man/as.list.listenv.Rd              |  27 +
 man/cash-.listenv.Rd                |  22 +
 man/cash-set-.listenv.Rd            |  21 +
 man/get_variable.Rd                 |  27 +
 man/length.listenv.Rd               |  16 +
 man/listenv.Rd                      |  33 ++
 man/map.Rd                          |  20 +
 man/names.listenv.Rd                |  17 +
 man/parse_env_subset.Rd             |  24 +
 man/undim.Rd                        |  27 +
 tests/as.listenv.R                  |  33 ++
 tests/get_variable,dimensions.R     |  38 ++
 tests/get_variable.R                | 100 ++++
 tests/listenv,dimensions.R          | 303 ++++++++++++
 tests/listenv.R                     | 692 ++++++++++++++++++++++++++
 tests/parse_env_subset,dimensions.R | 105 ++++
 tests/parse_env_subset.R            | 222 +++++++++
 tests/undim.R                       |  33 ++
 tests/utils.R                       |  43 ++
 vignettes/listenv.md.rsp            | 360 ++++++++++++++
 32 files changed, 4697 insertions(+)

diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000..4960961
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,20 @@
+Package: listenv
+Version: 0.6.0
+Depends: R (>= 3.1.2)
+Suggests: R.utils, R.rsp
+VignetteBuilder: R.rsp
+Title: Environments Behaving (Almost) as Lists
+Authors at R: c(person("Henrik", "Bengtsson", role=c("aut", "cre", "cph"),
+                                          email = "henrikb at braju.com"))
+Description: List environments are environments that have list-like properties.  For instance, the elements of a list environment are ordered and can be accessed and iterated over using index subsetting, e.g. 'x <- listenv(a=1, b=2); for (i in seq_along(x)) x[[i]] <- x[[i]]^2; y <- as.list(x)'.
+License: LGPL (>= 2.1)
+LazyLoad: TRUE
+URL: https://github.com/HenrikBengtsson/listenv
+BugReports: https://github.com/HenrikBengtsson/listenv/issues
+RoxygenNote: 5.0.1
+NeedsCompilation: no
+Packaged: 2015-12-27 23:02:54 UTC; hb
+Author: Henrik Bengtsson [aut, cre, cph]
+Maintainer: Henrik Bengtsson <henrikb at braju.com>
+Repository: CRAN
+Date/Publication: 2015-12-28 00:07:26
diff --git a/MD5 b/MD5
new file mode 100644
index 0000000..de276cf
--- /dev/null
+++ b/MD5
@@ -0,0 +1,31 @@
+e745d634585a29c2e525cfb3d39d59e8 *DESCRIPTION
+89e15c945dcfb605128c7abdc0f68f80 *NAMESPACE
+c9f8fda1ddd2984c19a67b0ce7a20210 *NEWS
+f06dc34d2a1839d07cd98d30600e3c26 *R/get_variable.R
+7a9462d34a31c2577bdd583fec4d052a *R/listenv.R
+5fa2a45fdd2adb019d52f36b466cd268 *R/parse_env_subset.R
+fe8db3909366f82cc1d31603046f0e92 *R/undim.R
+0b750b7626b75191087db81e86f7e731 *R/utils.R
+950222ec3362beed946618f55d5254dd *build/vignette.rds
+56d74cee9b8f35a17dd7a98ab0c16e8c *inst/doc/listenv.html
+43ebcd44126cb94d23ac395145ba2444 *inst/doc/listenv.md.rsp
+db64844e07ab14c995f6116fcfe170df *man/as.list.listenv.Rd
+aeff557461299c3b2e5bcd5968991af1 *man/cash-.listenv.Rd
+3764c89fb96ace37631c81fd22a3e638 *man/cash-set-.listenv.Rd
+dcd80183ea3e1c1b87b6785b59536880 *man/get_variable.Rd
+90ca995bbbc8ac59ad49c3202eeed63e *man/length.listenv.Rd
+957163178691b32d4a5987d96abd430c *man/listenv.Rd
+f7a3e638c5ddc9075772ccc2c1604838 *man/map.Rd
+563213dc5b6d707bcc0367a0321c1396 *man/names.listenv.Rd
+d6870fe0e2c4089f7d483db848ab3a74 *man/parse_env_subset.Rd
+cbdcda4780136f1f2a35ed42b39d8f31 *man/undim.Rd
+06b8c0ea74d231f933dd0ccda150594f *tests/as.listenv.R
+384d4562acfd09d1a0e8b0970d01f745 *tests/get_variable,dimensions.R
+13f5f5c5c2cb4965c35f27d628463776 *tests/get_variable.R
+8e40808adef445b921d0edd5b17d8ffa *tests/listenv,dimensions.R
+6a67a4b0a46b2be6452fc02b33956828 *tests/listenv.R
+d256b53faf4e45c2765fc8707dcdc589 *tests/parse_env_subset,dimensions.R
+b76a7090fb365a74664dbb2b1ed63597 *tests/parse_env_subset.R
+07e4d80d68932de4faf92db1e9058d0f *tests/undim.R
+3ff218c62aa9169c129c7d4e5b6c0ec6 *tests/utils.R
+43ebcd44126cb94d23ac395145ba2444 *vignettes/listenv.md.rsp
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000..48d9993
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,33 @@
+# Generated by roxygen2: do not edit by hand
+
+S3method("$",listenv)
+S3method("$<-",listenv)
+S3method("[",listenv)
+S3method("[<-",listenv)
+S3method("[[",listenv)
+S3method("[[<-",listenv)
+S3method("dim<-",listenv)
+S3method("dimnames<-",listenv)
+S3method("length<-",listenv)
+S3method("names<-",listenv)
+S3method(all.equal,listenv)
+S3method(as.list,listenv)
+S3method(as.listenv,default)
+S3method(as.listenv,environment)
+S3method(as.listenv,list)
+S3method(as.listenv,listenv)
+S3method(dim,listenv)
+S3method(dimnames,listenv)
+S3method(get_variable,listenv)
+S3method(length,listenv)
+S3method(names,listenv)
+S3method(print,listenv)
+S3method(undim,default)
+S3method(undim,listenv)
+S3method(unlist,listenv)
+export(as.listenv)
+export(get_variable)
+export(listenv)
+export(map)
+export(parse_env_subset)
+export(undim)
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..9314196
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,65 @@
+Package: listenv
+================
+
+Version: 0.6.0 [2015-12-27]
+o Added support for multi-dimensional subsetting of list environments
+  just as for list.
+o BUG FIX: parse_env_subset(x[[idx]]) for list environment 'x' and
+  index 'idx' claimed x[[idx]] exists as long as idx in [1,length(x)]
+  but it forgot to check if element really existed, which may not
+  be true if 'x' has been expanded.
+
+
+Version: 0.5.0 [2015-10-30]
+o Add support for assigning elements when creating list environment
+  similarly how to lists work, e.g. x <- listenv(a=1, b=2).
+o length(x) <- n now expand/truncate a list environment.
+o Added unlist() and all.equal() for list environments.
+o DEPRECATED: Deprecated x <- listenv(length=n). Instead use
+  x <- listenv(); length(x) <- n.
+o BUG FIX: as.listenv(x) would drop NULL elements in 'x'.
+o BUG FIX: x[idxs], x[name] <- y and x$<name> <- y would introduce
+  NA names for non-named list environments.
+
+
+Version: 0.4.0 [2015-08-08]
+o Added as.listenv().
+o CONSISTENCY: Assigning NULL now removes element just as lists,
+  e.g. x$a <- NULL. To assign value NULL, do x['a'] <- list(NULL).
+o Added support for subsetting with [(), which returns another
+  list environment, e.g. x[2:3], x[-1] and x[c(TRUE, FALSE)].
+o Added [<- assignment, e.g. x['a'] <- 1 and x[2:3] <- c(3,8).
+o CLEANUP: Dropped stray debug code.
+
+
+Version: 0.3.0 [2015-05-23]
+o Package no longer depends on other packages.
+
+
+Version: 0.2.4 [2015-05-22]
+o Added helper function parse_env_subset().
+
+
+Version: 0.2.3 [2015-05-21]
+o print() on listenv() handles empty and no-named listenv:s better.
+
+
+Version: 0.2.2 [2015-05-20]
+o Now listenv(length=...) always allocates internal variables.
+
+
+Version: 0.2.1 [2015-05-19]
+o get_variable() gained argument 'mustExist'.
+
+
+Version: 0.2.0 [2015-05-19]
+o Moved list environments from an in-house package to its own package.
+
+
+Version: 0.1.4 [2015-05-02]
+o Added print() for listenv:s.
+o CLEANUP: Using tempvar() of R.utils.
+
+
+Version: 0.1.0 [2015-02-07]
+o Created.
diff --git a/R/get_variable.R b/R/get_variable.R
new file mode 100644
index 0000000..c506c04
--- /dev/null
+++ b/R/get_variable.R
@@ -0,0 +1,87 @@
+#' Get name of variable for a specific element of list environment
+#'
+#' @param x A list environment.
+#' @param name The name or index of element of interest.
+#' @param mustExist If TRUE, an error is generated if \code{name}
+#'       does not exist.
+#' @param create If TRUE, element \code{name} is created if missing.
+#'
+#' @return The name of the underlying variable
+#'
+#' @aliases get_variable.listenv
+#' @export
+#' @keywords internal
+get_variable <- function(...) UseMethod("get_variable")
+
+#' @export
+get_variable.listenv <- function(x, name, mustExist=FALSE, create=!mustExist, ...) {
+  if (is.character(name)) {
+  } else if (is.numeric(name)) {
+  } else {
+    stop("Subscript must be a name or an index: ", mode(name), call.=FALSE)
+  }
+
+  dim <- dim(x)
+  if (is.null(dim)) {
+    if (length(name) != 1L) {
+      stop("Subscript must be a scalar: ", length(name), call.=FALSE)
+    }
+  } else {
+    ndim <- length(dim)
+    if (length(name) != 1L && length(name) != ndim) {
+      stop(sprintf("Subscript must be a scalar or of equal length as the number of dimension (%d): %d", ndim, length(name)), call.=FALSE)
+    }
+
+    ## Map multi-dimensional index to scalar index
+    if (length(name) > 1L) {
+      stopifnot(is.numeric(name))
+      idxs <- name
+      if (anyNA(idxs)) stop("Unknown index detected")
+
+      for (kk in seq_len(ndim)) {
+        if (idxs[kk] < 1 || idxs[kk] > dim[kk]) {
+          stop(sprintf("Index #%d out of range [1,%d]: %s", kk, dim[kk], idxs[kk]))
+        }
+      }
+      bases <- rev(c(cumprod(dim[-ndim]), 1))
+      idx <- sum(bases * (idxs-1)) + 1
+      name <- idx
+    }
+  }
+
+  map <- map(x)
+
+  ## Existing variable?
+  var <- map[name]
+  if (length(var) == 1L && !is.na(var)) return(var)
+
+  if (mustExist) {
+    stop(sprintf("No such %s element: %s", sQuote(class(x)[1]), name))
+  }
+
+  ## Create new variable
+  if (is.character(name)) {
+    var <- name
+
+    ## Non-existing name?
+    if (!is.element(name, names(map))) {
+      map <- c(map, var)
+      names(map)[length(map)] <- var
+    }
+  } else if (is.numeric(name)) {
+    i <- name
+    ## Expand map?
+    if (i > length(map)) {
+      extra <- rep(NA_character_, times=i-length(map))
+      map <- c(map, extra)
+    }
+    ## Create internal variable
+    var <- new_variable(x, value=NULL, create=create)
+    map[i] <- var
+  }
+
+  ## Update map?
+  if (create) map(x) <- map
+
+  var
+}
diff --git a/R/listenv.R b/R/listenv.R
new file mode 100644
index 0000000..78eae4e
--- /dev/null
+++ b/R/listenv.R
@@ -0,0 +1,960 @@
+#' Create a list environment
+#'
+#' @param \dots (optional) Named and/or unnamed objects to be
+#' assigned to the list environment.
+#'
+#' @return An environment of class `listenv`.
+#'
+#' @example incl/listenv.R
+#'
+#' @aliases as.listenv
+#' @export
+listenv <- function(...) {
+  args <- list(...)
+  nargs <- length(args)
+  names <- names(args)
+
+  ## Allocate empty list environment
+  metaenv <- new.env(parent=parent.frame())
+  env <- new.env(parent=metaenv)
+
+  ## Add elements?
+  if (nargs > 0L) {
+    ## Backward compatibility
+    if (nargs == 1L && identical(names[1L], "length")) {
+      .Deprecated(msg="Use of x <- listenv(length=n) to allocate a list environment of length n is deprecated. Use x <- listenv(); length(x) <- n instead.")
+      length <- args$length
+      stopifnot(length >= 0L)
+      args <- vector("list", length=length)
+      nargs <- length
+      names <- NULL
+    }
+  }
+
+  ## Allocate internal variables
+  maps <- sprintf(".listenv_var_%d", seq_len(nargs))
+  names(maps) <- names
+  for (kk in seq_len(nargs)) {
+    assign(maps[kk], value=args[[kk]], envir=env, inherits=FALSE)
+  }
+  metaenv$.listenv.map <- maps
+
+  assign(".listenv_var_count", nargs, envir=env, inherits=FALSE)
+
+  class(env) <- c("listenv", class(env))
+
+  env
+}
+
+#' @export
+#' @rdname listenv
+as.listenv <- function(...) UseMethod("as.listenv")
+
+#' @export
+as.listenv.listenv <- function(x, ...) {
+  x
+}
+
+#' @export
+as.listenv.list <- function(x, ...) {
+  nx <- length(x)
+  res <- listenv()
+  length(res) <- nx
+  names(res) <- names <- names(x)
+  for (kk in seq_len(nx)) {
+    value <- x[[kk]]
+    if (is.null(value)) value <- list(NULL)
+    res[[kk]] <- value
+  }
+
+  ## Set dimensions?
+  dim <- dim(x)
+  if (!is.null(dim)) {
+    dim(res) <- dim
+    dimnames(res) <- dimnames(x)
+    names(res) <- names
+  }
+
+  res
+}
+
+#' @export
+as.listenv.environment <- function(x, ...) {
+  as.listenv(as.list(x, ...))
+}
+
+#' @export
+as.listenv.default <- function(x, ...) {
+  as.listenv(as.list(x, ...))
+}
+
+
+#' @export
+print.listenv <- function(x, ...) {
+  n <- length(x)
+  dim <- dim(x)
+  ndim <- length(dim)
+  names <- names(x)
+  dimnames <- dimnames(x)
+  class <- class(x)[1L]
+
+  if (ndim <= 1) {
+    what <- "vector"
+  } else if (ndim == 2) {
+    what <- "matrix"
+  } else {
+    what <- "array"
+  }
+
+  s <- sprintf("A %s %s with %d", sQuote(class), what, n)
+  if (is.null(names) && n > 0) {
+    s <- sprintf("%s unnamed", s)
+  }
+  if (n == 1) {
+    s <- sprintf("%s element", s)
+  } else {
+    s <- sprintf("%s elements", s)
+  }
+  if (!is.null(names)) {
+    s <- sprintf("%s (%s)", s, hpaste(sQuote(names)))
+  }
+  if (ndim > 1) {
+    dimstr <- paste(dim, collapse="x")
+    hasDimnames <- !sapply(dimnames, FUN=is.null)
+    dimnamesT <- sapply(dimnames, FUN=function(x) hpaste(sQuote(x)))
+
+    s <- sprintf("%s arranged in %s", s, dimstr)
+
+    if (ndim == 2) {
+      if (is.null(dimnames)) {
+        s <- sprintf("%s unnamed rows and columns", s, dimstr)
+      } else {
+        if (all(hasDimnames)) {
+          s <- sprintf("%s rows (%s) and columns (%s)", s, dimnamesT[1L], dimnamesT[2L])
+        } else if (hasDimnames[1]) {
+          s <- sprintf("%s rows (%s) and unnamed columns", s, dimnamesT[1L])
+        } else if (hasDimnames[2]) {
+          s <- sprintf("%s unnamed rows and columns (%s)", s, dimnamesT[2L])
+        } else {
+          s <- sprintf("%s unnamed rows and columns", s, dimstr)
+        }
+      }
+    } else {
+      if (is.null(dimnames)) {
+        s <- sprintf("%s unnamed dimensions", s)
+      } else {
+        dimnamesT[!hasDimnames] <- "NULL"
+        dimnamesT <- sprintf("#%d: %s", seq_along(dimnamesT), dimnamesT)
+        dimnamesT <- paste(dimnamesT, collapse="; ")
+        if (all(hasDimnames)) {
+          s <- sprintf("%s dimensions (%s)", s, dimnamesT)
+        } else if (!any(hasDimnames)) {
+          s <- sprintf("%s unnamed dimensions", s)
+        } else {
+          s <- sprintf("%s partially named dimensions (%s)", s, dimnamesT)
+        }
+      }
+    }
+  }
+
+  s <- sprintf("%s.\n", s)
+  cat(s)
+}
+
+#' Variable name map for elements of list environment
+#'
+#' @param x A list environment.
+#'
+#' @return The a named character vector
+#'
+#' @aliases map.listenv
+#' @export
+#' @keywords internal
+map <- function(x, ...) {
+  get(".listenv.map", envir=parent.env(x), inherits=FALSE)
+}
+
+`map<-` <- function(x, value) {
+  stopifnot(is.character(value))
+  assign(".listenv.map", value, envir=parent.env(x), inherits=FALSE)
+  invisible(x)
+}
+
+#' Number of elements in list environment
+#'
+#' @param x A list environment.
+#'
+#' @export
+#' @keywords internal
+length.listenv <- function(x) {
+  length(map(x))
+}
+
+#' @export
+`length<-.listenv` <- function(x, value) {
+  map <- map(x)
+  n <- length(map)
+  value <- as.numeric(value)
+
+  if (value < 0) stop("invalid value")
+
+  ## Nothing to do?
+  if (value == n) return(invisible(x))
+
+  ## Expand or shrink?
+  if (value > n) {
+    ## Add place holders for added elements
+    extra <- rep(NA_character_, times=value-n)
+    map <- c(map, extra)
+  } else {
+    ## Drop existing variables
+    drop <- (value+1):n
+    var <- map[drop]
+    ## Some may be internal place holders
+    var <- var[!is.na(var)]
+    if (length(var) > 0) remove(list=var, envir=x, inherits=FALSE)
+    map <- map[-drop]
+  }
+  map(x) <- map
+
+  invisible(x)
+}
+
+
+#' Names of elements in list environment
+#'
+#' @param x A list environment.
+#'
+#' @aliases names<-.listenv
+#' @export
+#' @keywords internal
+names.listenv <- function(x) {
+  names(map(x))
+}
+
+#' @export
+`names<-.listenv` <- function(x, value) {
+  map <- map(x)
+  if (is.null(value)) {
+  } else if (length(value) != length(map)) {
+    stop(sprintf("Number of names does not match the number of elements: %s != %s", length(value), length(map)))
+  }
+##  if (any(duplicated(value))) {
+##    stop("Environments cannot have duplicate names on elements")
+##  }
+  names(map) <- value
+  map(x) <- map
+  invisible(x)
+}
+
+#' List representation of a list environment
+#'
+#' @param x A list environment.
+#' @param all.names If \code{TRUE}, variable names starting with
+#'        a period are included, otherwise not.
+#' @param sorted If \code{TRUE}, elements are ordered by their names
+#'        before being compared, otherwise not.
+#' @param ... Not used.
+#'
+#' @return A list.
+#'
+#' @export
+#' @keywords internal
+as.list.listenv <- function(x, all.names=TRUE, sorted=FALSE, ...) {
+  vars <- map(x)
+  nvars <- length(vars)
+  names <- names(x)
+
+  ## Drop names starting with a period
+  if (!all.names && nvars > 0) {
+    keep <- !grepl("^[.]", names)
+    vars <- vars[keep]
+    names <- names[keep]
+    nvars <- length(vars)
+  }
+
+  ## Sort by names?
+  if (sorted && nvars > 0) {
+    o <- order(names)
+    vars <- vars[o]
+    names <- names[o]
+  }
+
+  ## Collect as a named list
+  res <- vector("list", length=nvars)
+  names(res) <- names
+
+  if (nvars > 0) {
+    ok <- !is.na(vars)
+    res[ok] <- mget(vars[ok], envir=x, inherits=FALSE)
+  }
+
+  ## Set dimensions?
+  dim <- dim(x)
+  if (!is.null(dim)) {
+    dim(res) <- dim
+    dimnames(res) <- dimnames(x)
+    names(res) <- names
+  }
+
+  res
+}
+
+
+#' Get elements of list environment
+#'
+#' @param x A list environment.
+#' @param name The name or index of the element to retrieve.
+#'
+#' @return The value of an element or NULL if the element does not exist
+#'
+#' @aliases [[.listenv
+#' @aliases [.listenv
+#' @export
+#' @keywords internal
+`$.listenv` <- function(x, name) {
+#' @keywords internal
+  map <- map(x)
+  var <- map[name]
+
+  ## Non-existing variable?
+  if (is.na(var)) return(NULL)
+
+  get(var, envir=x, inherits=FALSE)
+}
+
+
+## [[i,j,...]] -> [[idx]]
+toIndex <- function(x, idxs) {
+  nidxs <- length(idxs)
+
+  dim <- dim(x)
+  if (is.null(dim)) dim <- length(x)
+  ndim <- length(dim)
+  if (ndim != nidxs) {
+    stop("incorrect number of dimensions")
+  }
+  dimnames <- dimnames(x)
+  idxDimnames <- dimnames
+
+  ## Indexing scale factor per dimension
+  scale <- c(1L, cumprod(dim[-ndim]))
+
+  ## Subset
+  idx <- 1
+  for (kk in 1:nidxs) {
+    i <- idxs[[kk]]
+    ni <- length(i)
+    if (is.character(i)) {
+      name <- i
+      i <- match(name, table=dimnames[[kk]])
+      if (anyNA(i)) stop("subscript out of bounds")
+    } else if (is.logical(i)) {
+      d <- dim[kk]
+      ni <- length(i)
+      if (ni > d) stop("(subscript) logical subscript too long")
+      if (ni < d) i <- rep(i, length.out=d)
+      i <- which(i)
+    } else if (is.numeric(i)) {
+      d <- dim[kk]
+      if (any(i > d)) stop("subscript out of bounds")
+      if (any(i < 0)) {
+        if (any(i > 0)) {
+          stop("only 0's may be mixed with negative subscripts")
+        }
+        ## Drop elements
+        i <- setdiff(seq_len(d), -i)
+      }
+      ## Drop zeros
+      i <- i[i != 0]
+    } else {
+      stop("invalid subscript type", sQuote(typeof(i)))
+    }
+
+    ## Subset dimnames?
+    if (!is.null(idxDimnames)) {
+      dn <- idxDimnames[[kk]]
+      if (!is.null(dn)) idxDimnames[[kk]] <- dn[i]
+    }
+
+    i <- scale[kk]*(i - 1)
+    if (kk == 1) {
+      idx <- idx + i
+    } else {
+      idx <- outer(idx, i, FUN=`+`)
+    }
+  } # for (kk ...)
+
+  ## Sanity check
+  dim <- dim(idx)
+  ndim <- length(dim)
+  if (ndim != nidxs) {
+    stop(sprintf("INTERNAL ERROR: Incompatible dimensions: %d != %d", ndim, nidxs))
+  }
+
+  ## Preserve names(dim)
+  names(dim(idx)) <- names(dim(x))
+
+  ## Preserve dimnames
+  dimnames(idx) <- idxDimnames
+
+
+  idx
+} # toIndex()
+
+
+#' @export
+`[[.listenv` <- function(x, ...) {
+  map <- map(x)
+  n <- length(map)
+
+  idxs <- list(...)
+  nidxs <- length(idxs)
+
+  ## Subsetting by multiple dimensions?
+  if (nidxs > 1L) {
+    i <- toIndex(x, idxs)
+  } else {
+    i <- idxs[[1L]]
+    if (is.character(i)) {
+      name <- i
+      i <- match(name, table=names(map))
+      if (is.na(i)) return(NULL)
+    } else if (!is.numeric(i)) {
+      return(NextMethod("[["))
+    }
+
+    if (length(i) != 1L) {
+      stop("Subsetting of more than one element at the time is not allowed for listenv's: ", length(i))
+    }
+
+    if (i < 1L || i > n) {
+      stop(sprintf("Subscript out of bounds [%d,%d]: %d", min(1,n), n, i), call.=FALSE)
+    }
+  }
+
+  var <- map[i]
+
+  ## Return default (NULL)?
+  if (is.na(var) || !exists(var, envir=x, inherits=FALSE)) return(NULL)
+
+  get(var, envir=x, inherits=FALSE)
+}
+
+
+#' @export
+`[.listenv` <- function(x, ..., drop=TRUE) {
+  ## Need to allow for implicit indices, e.g. x[1,,2]
+  idxs <- as.list(sys.call())[-(1:2)]
+  idxs$drop <- NULL
+  nidxs <- length(idxs)
+
+  ## Assert that subsetting has correct shape
+  dim <- dim(x)
+  ndim <- length(dim)
+  if (nidxs > 1 && nidxs != ndim) {
+    stop(sprintf("Incorrect subsetting. Expected %d dimensions but got %d", ndim, nidxs))
+  }
+
+  ## Implicitly specified dimensions
+  missing <- sapply(idxs, FUN=function(x) is.symbol(x) && identical("", deparse(x)))
+  if (any(missing)) {
+    if (nidxs == ndim) {
+      envir <- parent.frame()
+      for (kk in seq_len(ndim)) {
+        if (missing[kk]) {
+          idxs[[kk]] <- seq_len(dim[kk])
+        } else {
+          idxs[[kk]] <- eval(idxs[[kk]], envir=envir)
+        }
+      }
+    } else if (nidxs == 1) {
+      if (ndim == 0) {
+        idxs <- list(seq_len(length(x)))
+      } else {
+        ## Special case: Preserve dimensions when x[]
+        idxs <- lapply(dim, FUN=function(n) seq_len(n))
+        nidxs <- length(idxs)
+     }
+    }
+  } else {
+    envir <- parent.frame()
+    idxs <- lapply(idxs, FUN=eval, envir=envir)
+  }
+
+  if (nidxs <= 1L) {
+    i <- idxs[[1L]]
+  } else {
+    i <- toIndex(x, idxs)
+  }
+
+  map <- map(x)
+  nmap <- length(map)
+  names <- names(map)
+
+  if (is.null(i)) {
+    i <- integer(0L)
+  } else if (is.character(i)) {
+    name <- i
+    i <- match(name, table=names)
+  } else if (is.numeric(i)) {
+    ## Exclude elements with negative indices?
+    if (any(i < 0)) {
+      stopifnot(is.null(dim(i)))
+      if (any(i > 0)) {
+        stop("only 0's may be mixed with negative subscripts")
+      }
+      ## Drop elements
+      i <- setdiff(seq_len(nmap), -i)
+    }
+    ## Drop zeros?
+    if (is.null(dim(i))) {
+      i <- i[i != 0]
+    }
+  } else if (is.logical(i)) {
+    if (length(i) < nmap) i <- rep(i, length.out=nmap)
+    i <- which(i)
+  } else {
+    return(NextMethod("["))
+  }
+
+  ## Nothing to do?
+  ni <- length(i)
+
+  ## Allocate result
+  res <- listenv()
+  length(res) <- ni
+  res <- structure(res, class=class(x))
+
+  if (ni > 0L) {
+    ## Add names?
+    if (!is.null(names)) {
+      names2 <- names[i]
+      names2[i > nmap] <- ""
+      names(res) <- names2
+    }
+
+    ## Ignore out-of-range indices
+    j <- i[i <= nmap]
+    for (kk in seq_along(j)) {
+      value <- x[[j[kk]]]
+      if (!is.null(value)) res[[kk]] <- value
+    }
+  }
+
+  ## Preserve dimensions?
+  dim <- dim(i)
+  ndim <- length(dim)
+  if (ndim > 1) {
+    dimnames <- dimnames(i)
+
+    ## Drop singleton dimensions?
+    if (drop) {
+      keep <- (dim != 1)
+      dim <- dim[keep]
+      dimnames <- dimnames[keep]
+      ndim <- length(dim)
+    }
+
+    if (ndim > 1) {
+      names <- names(res)
+      dim(res) <- dim
+      dimnames(res) <- dimnames
+      names(res) <- names
+    }
+  }
+
+  res
+}
+
+
+new_variable <- function(envir, value, create=TRUE) {
+  count <- get(".listenv_var_count", envir=envir, inherits=FALSE)
+
+  count <- count + 1L
+  name <- sprintf(".listenv_var_%f", count)
+
+  if (!missing(value)) {
+    assign(name, value, envir=envir, inherits=FALSE)
+  }
+
+  if (create) {
+    assign(".listenv_var_count", count, envir=envir, inherits=FALSE)
+  }
+
+  name
+} # new_variable()
+
+
+assign_by_name <- function(x, name, value) {
+  ## Argument 'name':
+  if (length(name) == 0L) {
+    stop("Cannot assign value. Zero-length name.", call.=FALSE)
+  } else if (length(name) > 1L) {
+    stop("Cannot assign value. More than one name specified: ", hpaste(name), call.=FALSE)
+  } else if (nchar(name) == 0L) {
+    stop("Cannot assign value. Empty name specific: ", name, call.=FALSE)
+  }
+
+  map <- map(x)
+  names <- names(map)
+
+  ## Map to an existing or a new element?
+  if (is.element(name, names)) {
+    var <- map[name]
+
+    ## A new variable?
+    if (is.na(var)) {
+      var <- name
+      map[name] <- name
+      map(x) <- map
+    }
+  } else {
+    var <- name
+
+    ## Append to map
+    map <- c(map, var)
+    if (is.null(names)) names <- rep("", times=length(map))
+    names[length(map)] <- var
+    names(map) <- names
+    map(x) <- map
+  }
+
+  ## Assign value
+  assign(var, value, envir=x, inherits=FALSE)
+
+  invisible(x)
+} # assign_by_name()
+
+
+assign_by_index <- function(x, i, value) {
+  ## Argument 'i':
+  if (length(i) == 0L) {
+    stop("Cannot assign value. Zero-length index.", call.=FALSE)
+  } else if (length(i) > 1L) {
+    stop("Cannot assign value. More than one index specified: ", hpaste(i), call.=FALSE)
+  } else if (!is.finite(i)) {
+    stop("Cannot assign value. Non-finite index: ", i, call.=FALSE)
+  } else if (i < 1L) {
+    stop("Cannot assign value. Non-positive index: ", i, call.=FALSE)
+  }
+
+  map <- map(x)
+  n <- length(map)
+
+  ## Variable name
+  var <- map[i]
+
+  ## Non-existing variable?
+  if (is.na(var)) {
+    ## Expand map?
+    if (i > n) {
+      extra <- rep(NA_character_, times=i-n)
+      map <- c(map, extra)
+    }
+
+    ## Create internal variable
+    map[i] <- new_variable(x, value=value)
+
+    ## Update map
+    map(x) <- map
+  } else {
+    assign(var, value, envir=x, inherits=FALSE)
+  }
+
+  invisible(x)
+} # assign_by_index()
+
+
+remove_by_name <- function(x, name) {
+  ## Argument 'name':
+  if (length(name) == 0L) {
+    stop("Cannot remove element. Zero-length name.", call.=FALSE)
+  } else if (length(name) > 1L) {
+    stop("Cannot remove element. More than one name specified: ", hpaste(name), call.=FALSE)
+  } else if (nchar(name) == 0L) {
+    stop("Cannot remove element. Empty name specific: ", name, call.=FALSE)
+  }
+
+  map <- map(x)
+
+  ## Position in names map?
+  idx <- match(name, names(map))
+
+  ## Nothing to do?
+  if (is.na(idx)) return(invisible(x))
+
+  ## Drop internal variable, unless place holder
+  var <- map[idx]
+  if (!is.na(var)) remove(list=var, envir=x, inherits=FALSE)
+
+  map <- map[-idx]
+  map(x) <- map
+
+  invisible(x)
+} # remove_by_name()
+
+
+remove_by_index <- function(x, i) {
+  ## Argument 'i':
+  if (length(i) == 0L) {
+    stop("Cannot remove element. Zero-length index.", call.=FALSE)
+  } else if (length(i) > 1L) {
+    stop("Cannot remove element. More than one index specified: ", hpaste(i), call.=FALSE)
+  } else if (!is.finite(i)) {
+    stop("Cannot remove element. Non-finite index: ", i, call.=FALSE)
+  } else if (i < 1L) {
+    stop("Cannot remove element. Non-positive index: ", i, call.=FALSE)
+  }
+
+  map <- map(x)
+
+  ## Nothing to do?
+  if (i > length(map)) return(invisible(x))
+
+  ## Drop internal variable, unless place holder
+  var <- map[i]
+  if (!is.na(var)) remove(list=var, envir=x, inherits=FALSE)
+
+  map <- map[-i]
+  map(x) <- map
+
+  invisible(x)
+} # remove_by_index()
+
+
+
+
+#' Set an element of list environment
+#'
+#' @param x A list environment.
+#' @param name Name or index of element
+#' @param value The value to assign to the element
+#'
+#' @aliases [[<-.listenv
+#' @aliases [<-.listenv
+#' @export
+#' @keywords internal
+`$<-.listenv` <- function(x, name, value) {
+  if (is.null(value)) {
+    remove_by_name(x, name=name)
+  } else {
+    assign_by_name(x, name=name, value=value)
+  }
+}
+
+#' @export
+`[[<-.listenv` <- function(x, ..., value) {
+  map <- map(x)
+  n <- length(map)
+
+  idxs <- list(...)
+  nidxs <- length(idxs)
+
+  ## Subsetting by multiple dimensions?
+  if (nidxs > 1L) {
+    i <- toIndex(x, idxs)
+  } else {
+    i <- idxs[[1L]]
+    if (is.character(i)) {
+      if (is.null(value)) {
+        x <- remove_by_name(x, name=i)
+      } else {
+        x <- assign_by_name(x, name=i, value=value)
+      }
+      return(invisible(x))
+    }
+  }
+
+  if (is.numeric(i)) {
+    if (is.null(value)) {
+      x <- remove_by_index(x, i=i)
+    } else {
+      x <- assign_by_index(x, i=i, value=value)
+    }
+  } else {
+    stop(sprintf("Subsetted [[<- assignment to listenv's is only supported for names and indices, not %s", mode(i)), call.=FALSE)
+  }
+
+  return(invisible(x))
+}
+
+
+#' @export
+`[<-.listenv` <- function(x, ..., value) {
+  ## Need to allow for implicit indices, e.g. x[1,,2]
+  idxs <- as.list(sys.call())[-(1:2)]
+  idxs$value <- NULL
+  nidxs <- length(idxs)
+
+  ## Assert that subsetting has correct shape
+  dim <- dim(x)
+  ndim <- length(dim)
+  if (nidxs > 1 && nidxs != ndim) {
+    stop(sprintf("Incorrect subsetting. Expected %d dimensions but got %d", ndim, nidxs))
+  }
+
+  ## Implicitly specified dimensions
+  missing <- sapply(idxs, FUN=function(x) is.symbol(x) && identical("", deparse(x)))
+  if (any(missing)) {
+    if (nidxs == ndim) {
+      envir <- parent.frame()
+      for (kk in seq_len(ndim)) {
+        if (missing[kk]) {
+          idxs[[kk]] <- seq_len(dim[kk])
+        } else {
+          idxs[[kk]] <- eval(idxs[[kk]], envir=envir)
+        }
+      }
+    } else if (nidxs == 1) {
+      if (ndim == 0) {
+        idxs <- list(seq_len(length(x)))
+      } else {
+        ## Special case: Preserve dimensions when x[]
+        idxs <- lapply(dim, FUN=function(n) seq_len(n))
+        nidxs <- length(idxs)
+     }
+    }
+  } else {
+    envir <- parent.frame()
+    idxs <- lapply(idxs, FUN=eval, envir=envir)
+  }
+
+  if (nidxs <= 1L) {
+    i <- idxs[[1L]]
+  } else {
+    i <- toIndex(x, idxs)
+  }
+
+  ni <- length(i)
+  if (is.logical(i)) {
+    n <- length(x)
+    if (ni < n) i <- rep(i, length.out=n)
+    i <- which(i)
+    ni <- length(i)
+  }
+
+
+  # Nothing to do?
+  if (ni == 0L) return(invisible(x))
+
+  nvalue <- length(value)
+  if (nvalue == 0L) stop("Replacement has zero length", call.=FALSE)
+
+  if (ni != nvalue) {
+    if (ni < nvalue || ni %% nvalue != 0) {
+      warning(sprintf("Number of items to replace is not a multiple of replacement length: %d != %d", ni, nvalue), call.=FALSE)
+    }
+    value <- rep(value, length.out=ni)
+    nvalue <- length(value)
+  }
+
+  if (is.character(i)) {
+    for (kk in seq_len(ni)) {
+      x <- assign_by_name(x, name=i[kk], value=value[[kk]])
+    }
+  } else if (is.numeric(i)) {
+    for (kk in seq_len(ni)) {
+      x <- assign_by_index(x, i=i[kk], value=value[[kk]])
+    }
+  } else {
+    stop(sprintf("Subsetted [<- assignment to listenv's is only supported for names and indices, not %s", mode(i)), call.=FALSE)
+  }
+  return(invisible(x))
+}
+
+
+#' @export
+#' @method unlist listenv
+unlist.listenv <- function(x, recursive=TRUE, use.names=TRUE) {
+  names <- names(x)
+  x <- as.list(x)
+  names(x) <- names
+
+  if (recursive) {
+    repeat {
+      x <- unlist(x, recursive=TRUE, use.names=use.names)
+      idxs <- unlist(lapply(x, FUN=inherits, "listenv"), use.names=FALSE)
+      if (length(idxs) == 0L) break
+      idxs <- which(idxs)
+      if (length(idxs) == 0L) break
+      for (ii in idxs) {
+        x[[ii]] <- unlist(x[[ii]], recursive=TRUE, use.names=use.names)
+      }
+    }
+    x
+  } else {
+    unlist(x, recursive=FALSE, use.names=use.names)
+  }
+}
+
+#' @export
+dim.listenv <- function(x) attr(x, "dim.")
+
+#' @export
+`dim<-.listenv` <- function(x, value) {
+  n <- length(x)
+  if (!is.null(value)) {
+    names <- names(value)
+    value <- as.integer(value)
+    p <- prod(as.double(value))
+    if (p != n) {
+      stop(sprintf("dims [product %d] do not match the length of object [%d]", p, n))
+    }
+    names(value) <- names
+  }
+
+  ## Always remove "dimnames" and "names" attributes, cf. help("dim")
+  dimnames(x) <- NULL
+  names(x) <- NULL
+
+  attr(x, "dim.") <- value
+  x
+}
+
+
+#' @export
+dimnames.listenv <- function(x) attr(x, "dimnames.")
+
+#' @export
+`dimnames<-.listenv` <- function(x, value) {
+  dim <- dim(x)
+  if (is.null(dim) && !is.null(value)) {
+    stop("'dimnames' applied to non-array")
+  }
+  for (kk in seq_along(dim)) {
+    names <- value[[kk]]
+    if (is.null(names)) next
+    n <- length(names)
+    if (n != dim[kk]) {
+      stop(sprintf("length of 'dimnames' [%d] not equal to array extent", kk))
+    }
+  }
+  attr(x, "dimnames.") <- value
+  x
+}
+
+#' @export
+#' @method all.equal listenv
+all.equal.listenv <- function(target, current, all.names=TRUE, sorted=FALSE, ...) {
+  if (identical(target, current)) return(TRUE)
+
+  ## Coerce to lists
+  target <- as.list(target, all.names=all.names, sorted=sorted)
+  current <- as.list(current, all.names=all.names, sorted=sorted)
+
+  ## Not all as.list() methods support 'all.names'
+  if (!all.names) {
+    keep <-
+    target <- target[!grepl("^[.]", names(target))]
+    current <- current[!grepl("^[.]", names(current))]
+  }
+
+  ## Not all as.list() methods support 'sorted'
+  if (sorted) {
+    target <- target[order(names(target))]
+    current <- current[order(names(current))]
+  }
+
+  all.equal(target=target, current=current, ...)
+}
diff --git a/R/parse_env_subset.R b/R/parse_env_subset.R
new file mode 100644
index 0000000..3a02d36
--- /dev/null
+++ b/R/parse_env_subset.R
@@ -0,0 +1,269 @@
+#' Helper function to infer target from expression and environment
+#'
+#' @param expr An expression.
+#' @param envir An environment.
+#' @param substitute If TRUE, then the expression is
+#'        \code{substitute()}:ed, otherwise not.
+#'
+#' @return A named list.
+#'
+#' @export
+#' @keywords internal
+parse_env_subset <- function(expr, envir=parent.frame(), substitute=TRUE) {
+  if (substitute) expr <- substitute(expr)
+  code <- paste(deparse(expr), collapse="")
+
+  res <- list(envir=envir, name="", op=NULL, subset=NULL, idx=NA_integer_, exists=NA, code=code)
+
+  if (is.symbol(expr)) {
+    ## Variable specified as a symbol
+    res$name <- deparse(expr)
+  } else if (is.character(expr)) {
+    ## Variable specified as a name
+    if (length(expr) > 1L) {
+      stop(sprintf("Does not specify a single variable, but %d: %s", length(expr), hpaste(sQuote(expr), collapse=", ")), call.=FALSE)
+    }
+    res$name <- expr
+  } else if (is.numeric(expr)) {
+    ## Variable specified as a subset of envir
+    if (length(expr) > 1L) {
+      stop(sprintf("Does not specify a single index, but %d: %s", length(expr), hpaste(sQuote(expr), collapse=", ")), call.=FALSE)
+    }
+    res$subset <- list(expr)
+  } else {
+    n <- length(expr)
+    stopifnot(n >= 2L)
+
+    if (n >= 3L) {
+      ## Assignment to enviroment via $ and [[
+      op <- as.character(expr[[1]])
+      res$op <- op
+      if (op == "$" && n > 3L) {
+        stop("Invalid syntax: ", sQuote(code), call.=FALSE)
+      } else if (!is.element(op, c("$", "[[", "["))) {
+        stop("Invalid syntax: ", sQuote(code), call.=FALSE)
+      }
+
+      ## Target
+      objname <- deparse(expr[[2]])
+      if (!exists(objname, envir=envir, inherits=TRUE)) {
+        stop(sprintf("Object %s not found: %s", sQuote(objname), sQuote(code)), call.=FALSE)
+      }
+
+      obj <- get(objname, envir=envir, inherits=TRUE)
+      if (!is.environment(obj)) {
+        stop(sprintf("Subsetting can not be done on a %s; only to an environment: %s", sQuote(mode(obj)), sQuote(code)), call.=FALSE)
+      }
+      res$envir <- obj
+
+      ## Subset
+      subset <- list()
+      for (kk in 3:n) {
+        missing <- (length(expr[[kk]]) == 1L) && (expr[[kk]] == "")
+        if (missing) {
+          subsetKK <- NULL
+        } else {
+          subsetKK <- expr[[kk]]
+        }
+        if (is.symbol(subsetKK)) {
+          subsetKK <- deparse(subsetKK)
+          if (op == "[[") {
+            if (!exists(subsetKK, envir=envir, inherits=TRUE)) {
+              stop(sprintf("Object %s not found: %s", sQuote(subsetKK), sQuote(code)), call.=FALSE)
+            }
+            subsetKK <- get(subsetKK, envir=envir, inherits=TRUE)
+          }
+        } else if (is.language(subsetKK)) {
+          subsetKK <- eval(subsetKK, envir=envir)
+        }
+        if (is.null(subsetKK)) {
+          subset[kk-2L] <- list(NULL)
+        } else {
+          subset[[kk-2L]] <- subsetKK
+        }
+      }
+
+      res$subset <- subset
+    } # if (n >= 3)
+  } # if (is.symbol(expr))
+
+
+  ## Validat name, iff any
+  name <- res$name
+  if (nzchar(name) && !grepl("^[.a-zA-Z]+", name)) stop("Not a valid variable name: ", sQuote(name), call.=FALSE)
+
+
+  ## Validate subsetting, e.g. x[[1]], x[["a"]], and x$a, iff any
+  subset <- res$subset
+  if (!is.null(subset)) {
+    if (!is.list(subset)) {
+      stop(sprintf("INTERNAL ERROR (expected 'subset' to be a list): %s", sQuote(code)), call.=FALSE)
+    }
+    if (length(subset) == 0L) {
+      stop(sprintf("Subsetting of at least on element is required: %s", sQuote(code)), call.=FALSE)
+    }
+
+    for (kk in seq_along(subset)) {
+      subsetKK <- subset[[kk]]
+      if (is.null(subsetKK)) {
+      } else if (any(is.na(subsetKK))) {
+        stop(sprintf("Invalid subsetting. Subset must not contain missing values: %s", sQuote(code)), call.=FALSE)
+      } else if (is.character(subsetKK)) {
+        if (!all(nzchar(subsetKK))) {
+          stop(sprintf("Invalid subset. Subset must not contain empty names: %s", sQuote(code)), call.=FALSE)
+        }
+      } else if (is.numeric(subsetKK)) {
+      } else {
+        stop(sprintf("Invalid subset of type %s: %s", sQuote(typeof(subsetKK)), sQuote(code)), call.=FALSE)
+      }
+    } # for (kk ...)
+
+    ## Special: listenv:s
+    envir <- res$envir
+    stopifnot(is.environment(envir))
+
+    if (inherits(envir, "listenv")) {
+      names <- names(envir)
+      map <- map(envir)
+      dim <- dim(envir)
+
+      op <- res$op
+      if (is.null(op)) op <- "[["
+
+      ## Multi-dimensional subsetting?
+      if (length(subset) > 1L) {
+        if (is.null(dim)) {
+          stop("Multi-dimensional subsetting on list environment without dimensions: ", sQuote(code), call.=TRUE)
+        }
+        dimnames <- dimnames(envir)
+        exists <- TRUE
+        for (kk in seq_along(subset)) {
+          subsetKK <- subset[[kk]]
+          if (is.null(subsetKK)) {
+            subset[[kk]] <- seq_len(dim[kk])
+          } else if (is.numeric(subsetKK)) {
+            exists <- exists && (subsetKK >= 1 && subsetKK <= dim[kk])
+          } else if (is.character(subsetKK)) {
+            subsetKK <- match(subsetKK, dimnames[[kk]])
+            exists <- exists && !is.na(subsetKK)
+            subset[[kk]] <- subsetKK
+          }
+        }
+
+        ## Indexing scale factor per dimension
+        ndim <- length(dim)
+        scale <- c(1L, cumprod(dim[-ndim]))
+        idx <- 1
+        for (kk in seq_along(subset)) {
+          i <- subset[[kk]]
+          stopifnot(is.numeric(i))
+          d <- dim[kk]
+          if (any(i < 0)) {
+            if (op == "[[") {
+              stop("Invalid (negative) indices: ", hpaste(i))
+            } else if (any(i > 0)) {
+              stop("only 0's may be mixed with negative subscripts")
+            }
+            ## Drop elements
+            i <- setdiff(seq_len(d), -i)
+          }
+          if (any(i > d)) i[i > d] <- NA_integer_
+          ## Drop zeros
+          i <- i[i != 0]
+          i <- scale[kk]*(i - 1)
+          if (kk == 1) {
+            idx <- idx + i
+          } else {
+            idx <- outer(idx, i, FUN=`+`)
+          }
+        } # for (kk ...)
+
+        res$idx <- idx
+        res$name <- names[res$idx]
+        if (length(res$name) == 0L) res$name <- ""
+        if (exists) {
+          exists <- !is.na(map[idx])
+        }
+        res$exists <- exists
+      } else {
+        subset <- subset[[1L]]
+        if (is.numeric(subset)) {
+          i <- subset
+          n <- length(envir)
+          if (any(i < 0)) {
+            if (op == "[[") {
+              stop("Invalid (negative) indices: ", hpaste(i))
+            } else if (any(i > 0)) {
+              stop("only 0's may be mixed with negative subscripts")
+            }
+            ## Drop elements
+            i <- setdiff(seq_len(n), -i)
+          }
+          ## Drop zeros?
+          keep <- which(i != 0)
+          if (length(keep) != length(i)) {
+            if (op == "[[") {
+              ## BACKWARD COMPATIBILITY:
+              ## In order not to break two `R CMD check` package tests
+              ## for future 0.9.0 on CRAN, we tweak the result here in
+              ## order for those two tests not to fail. /HB 2015-12-26
+              ## FIX ME: Remove when future (> 0.9.0) is on CRAN.
+              if (identical(i, 0) && identical(code, "x[[0]]") && is.element("package:future", search()) && utils::packageVersion("future") <= "0.9.0") {
+                res$idx <- i
+                res$exists <- FALSE
+                return(res)
+              }
+              stop("Invalid (zero) indices: ", hpaste(i))
+            }
+            i <- i[keep]
+          }
+          res$idx <- i
+          res$exists <- !is.na(map[res$idx]) & (res$idx >= 1 & res$idx <= n)
+          res$name <- names[i]
+          if (length(res$name) == 0L) res$name <- ""
+        } else if (is.character(subset)) {
+          res$idx <- match(subset, names)
+          res$exists <- !is.na(res$idx) && !is.na(map[res$idx])
+        }
+      }
+    } else {
+      if (length(subset) > 1L) {
+        stop("Invalid subset: ", sQuote(code), call.=TRUE)
+      }
+      subset <- subset[[1L]]
+    }
+    if (is.character(subset)) res$name <- subset
+  }
+
+  ## Identify index?
+  if (inherits(res$envir, "listenv")) {
+    envir <- res$envir
+    if (any(is.na(res$idx)) && nzchar(res$name)) {
+      res$idx <- match(res$name, names(envir))
+    }
+    res$exists <- !is.na(res$idx) & !is.na(map(envir)[res$idx])
+  }
+
+  ## Validate
+  if (is.null(dim) && length(res$subset) == 1 && identical(res$op, "[")) {
+    if (any(is.na(res$idx)) && !nzchar(res$name)) {
+      stop("Invalid subset: ", sQuote(code), call.=TRUE)
+    }
+  }
+
+  unknown <- which(is.na(res$exists))
+  if (length(unknown) > 0) {
+    res$exists[unknown] <- sapply(unknown, FUN=function(idx) {
+      exists(res$name[idx], envir=res$envir, inherits=TRUE)
+    })
+  }
+
+  ## Sanity check
+  stopifnot(is.environment(res$envir))
+  stopifnot(is.character(res$name))
+  stopifnot(is.null(res$idx) || all(is.numeric(res$idx)))
+  stopifnot(is.logical(res$exists), !anyNA(res$exists))
+  stopifnot(length(res$exists) == length(res$idx))
+
+  res
+}
diff --git a/R/undim.R b/R/undim.R
new file mode 100644
index 0000000..1bfef77
--- /dev/null
+++ b/R/undim.R
@@ -0,0 +1,34 @@
+#' Removes the dimension of an object
+#'
+#' @param x An object with or without dimensions
+#' @param ... Not used.
+#'
+#' @return The object with the dimension attribute removed.
+#'
+#' @details
+#' This function does \code{attr(x, "dim") <- NULL}, which
+#' automatically also does \code{attr(x, "dimnames") <- NULL}.
+#' However, other attributes such as names attributes are preserved,
+#' which is not the case if one do \code{dim(x) <- NULL}.
+#'
+#' @export
+#' @aliases undim.default
+#' @aliases undim.listenv
+undim <- function(x, ...) UseMethod("undim")
+
+#' @export
+undim.default <- function(x, ...) {
+  if (is.null(dim(x))) return(x)
+  attr(x, "dim") <- NULL
+  ## Dimnames seems to be unset above, but in case it changes ...
+  attr(x, "dimnames") <- NULL
+  x
+}
+
+#' @export
+undim.listenv <- function(x, ...) {
+  x <- NextMethod("undim")
+  attr(x, "dim.") <- NULL
+  attr(x, "dimnames.") <- NULL
+  x
+}
diff --git a/R/utils.R b/R/utils.R
new file mode 100644
index 0000000..937423e
--- /dev/null
+++ b/R/utils.R
@@ -0,0 +1,33 @@
+## From R.utils 2.0.2 (2015-05-23)
+hpaste <- function(..., sep="", collapse=", ", lastCollapse=NULL, maxHead=if (missing(lastCollapse)) 3 else Inf, maxTail=if (is.finite(maxHead)) 1 else Inf, abbreviate="...") {
+  maxHead <- as.double(maxHead)
+  maxTail <- as.double(maxTail)
+  if (is.null(lastCollapse)) lastCollapse <- collapse
+
+  # Build vector 'x'
+  x <- paste(..., sep=sep)
+  n <- length(x)
+
+  # Nothing todo?
+  if (n == 0) return(x)
+  if (is.null(collapse)) return(x)
+
+  # Abbreviate?
+  if (n > maxHead + maxTail + 1) {
+    head <- x[seq(length=maxHead)]
+    tail <- rev(rev(x)[seq(length=maxTail)])
+    x <- c(head, abbreviate, tail)
+    n <- length(x)
+  }
+
+  if (!is.null(collapse) && n > 1) {
+    if (lastCollapse == collapse) {
+      x <- paste(x, collapse=collapse)
+    } else {
+      xT <- paste(x[1:(n-1)], collapse=collapse)
+      x <- paste(xT, x[n], sep=lastCollapse)
+    }
+  }
+
+  x
+} # hpaste()
diff --git a/build/vignette.rds b/build/vignette.rds
new file mode 100644
index 0000000..bd092ae
Binary files /dev/null and b/build/vignette.rds differ
diff --git a/inst/doc/listenv.html b/inst/doc/listenv.html
new file mode 100644
index 0000000..de3fd86
--- /dev/null
+++ b/inst/doc/listenv.html
@@ -0,0 +1,642 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+
+<title>List Environments</title>
+
+<script type="text/javascript">
+window.onload = function() {
+  var imgs = document.getElementsByTagName('img'), i, img;
+  for (i = 0; i < imgs.length; i++) {
+    img = imgs[i];
+    // center an image if it is the only element of its parent
+    if (img.parentElement.childElementCount === 1)
+      img.parentElement.style.textAlign = 'center';
+  }
+};
+</script>
+
+<!-- Styles for R syntax highlighter -->
+<style type="text/css">
+   pre .operator,
+   pre .paren {
+     color: rgb(104, 118, 135)
+   }
+
+   pre .literal {
+     color: #990073
+   }
+
+   pre .number {
+     color: #099;
+   }
+
+   pre .comment {
+     color: #998;
+     font-style: italic
+   }
+
+   pre .keyword {
+     color: #900;
+     font-weight: bold
+   }
+
+   pre .identifier {
+     color: rgb(0, 0, 0);
+   }
+
+   pre .string {
+     color: #d14;
+   }
+</style>
+
+<!-- R syntax highlighter -->
+<script type="text/javascript">
+var hljs=new function(){function m(p){return p.replace(/&/gm,"&").replace(/</gm,"<")}function f(r,q,p){return RegExp(q,"m"+(r.cI?"i":"")+(p?"g":""))}function b(r){for(var p=0;p<r.childNodes.length;p++){var q=r.childNodes[p];if(q.nodeName=="CODE"){return q}if(!(q.nodeType==3&&q.nodeValue.match(/\s+/))){break}}}function h(t,s){var p="";for(var r=0;r<t.childNodes.length;r++){if(t.childNodes[r].nodeType==3){var q=t.childNodes[r].nodeValue;if(s){q=q.replace(/\n/g,"")}p+=q}else{if(t.chi [...]
+hljs.initHighlightingOnLoad();
+</script>
+
+
+
+<style type="text/css">
+body, td {
+   font-family: sans-serif;
+   background-color: white;
+   font-size: 13px;
+}
+
+body {
+  max-width: 800px;
+  margin: auto;
+  padding: 1em;
+  line-height: 20px;
+}
+
+tt, code, pre {
+   font-family: 'DejaVu Sans Mono', 'Droid Sans Mono', 'Lucida Console', Consolas, Monaco, monospace;
+}
+
+h1 {
+   font-size:2.2em;
+}
+
+h2 {
+   font-size:1.8em;
+}
+
+h3 {
+   font-size:1.4em;
+}
+
+h4 {
+   font-size:1.0em;
+}
+
+h5 {
+   font-size:0.9em;
+}
+
+h6 {
+   font-size:0.8em;
+}
+
+a:visited {
+   color: rgb(50%, 0%, 50%);
+}
+
+pre, img {
+  max-width: 100%;
+}
+pre {
+  overflow-x: auto;
+}
+pre code {
+   display: block; padding: 0.5em;
+}
+
+code {
+  font-size: 92%;
+  border: 1px solid #ccc;
+}
+
+code[class] {
+  background-color: #F8F8F8;
+}
+
+table, td, th {
+  border: none;
+}
+
+blockquote {
+   color:#666666;
+   margin:0;
+   padding-left: 1em;
+   border-left: 0.5em #EEE solid;
+}
+
+hr {
+   height: 0px;
+   border-bottom: none;
+   border-top-width: thin;
+   border-top-style: dotted;
+   border-top-color: #999999;
+}
+
+ at media print {
+   * {
+      background: transparent !important;
+      color: black !important;
+      filter:none !important;
+      -ms-filter: none !important;
+   }
+
+   body {
+      font-size:12pt;
+      max-width:100%;
+   }
+
+   a, a:visited {
+      text-decoration: underline;
+   }
+
+   hr {
+      visibility: hidden;
+      page-break-before: always;
+   }
+
+   pre, blockquote {
+      padding-right: 1em;
+      page-break-inside: avoid;
+   }
+
+   tr, img {
+      page-break-inside: avoid;
+   }
+
+   img {
+      max-width: 100% !important;
+   }
+
+   @page :left {
+      margin: 15mm 20mm 15mm 10mm;
+   }
+
+   @page :right {
+      margin: 15mm 10mm 15mm 20mm;
+   }
+
+   p, h2, h3 {
+      orphans: 3; widows: 3;
+   }
+
+   h2, h3 {
+      page-break-after: avoid;
+   }
+}
+</style>
+
+<meta name="keywords" content="R, package, vignette, listenv">
+<meta name="author" content="Henrik Bengtsson">
+
+</head>
+
+<body>
+<h1>List Environments</h1>
+
+<p><em>List environments</em> are environments that have list-like properties.  They are implemented by the <a href="http://cran.r-project.org/package=listenv">listenv</a> package.  The main features of a list environment are summarized in the below table:</p>
+
+<table><thead>
+<tr>
+<th>Property</th>
+<th align="center">list environments</th>
+<th align="center">lists</th>
+<th align="center">environments</th>
+</tr>
+</thead><tbody>
+<tr>
+<td>Number of elements, e.g. <code>length()</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+</tr>
+<tr>
+<td>Named elements, e.g. <code>names()</code>, <code>x$a</code> and <code>x[["a"]]</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+</tr>
+<tr>
+<td>Duplicated names</td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Indexed elements, e.g. <code>x[[4]]</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Dimensions, e.g. <code>dim(x)</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Names of dimensions, e.g. <code>dimnames(x)</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Indexing by dimensions, e.g. <code>x[[2,4]]</code> and <code>x[[2,"D"]]</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Multi-element subsetting, e.g. <code>x[c("a", "c")]</code>, <code>x[-1]</code>, <code>[2:1,,3]</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Multi-element subsetting preserves element names</td>
+<td align="center">yes</td>
+<td align="center"></td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Removing element by assigning NULL, e.g. <code>x$c <- NULL</code></td>
+<td align="center">yes</td>
+<td align="center">yes</td>
+<td align="center"></td>
+</tr>
+<tr>
+<td>Mutable, e.g. <code>y <- x; y$a <- 3; identical(y, x)</code></td>
+<td align="center">yes</td>
+<td align="center"></td>
+<td align="center">yes</td>
+</tr>
+<tr>
+<td>Compatible* with <code>assign()</code>, <code>delayedAssign()</code>, <code>get()</code> and <code>exists()</code></td>
+<td align="center">yes</td>
+<td align="center"></td>
+<td align="center">yes</td>
+</tr>
+</tbody></table>
+
+<p>For example,</p>
+
+<pre><code class="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
+</code></pre>
+
+<h2>Creating list environments</h2>
+
+<p>List environments are created similarly to lists but also similarly to environments.  To create an empty list environment, use</p>
+
+<pre><code class="r">> x <- listenv()
+> x
+A 'listenv' vector with 0 elements.
+</code></pre>
+
+<p>This can later can be populated using named assignments,</p>
+
+<pre><code class="r">> x$a <- 1
+> x
+A 'listenv' vector with 1 element ('a').
+</code></pre>
+
+<p>comparable to how both lists and environments work.  Similarly to lists, they can also be populated using indices, e.g.</p>
+
+<pre><code class="r">> x[[2]] <- 2
+> x$c <- 3
+> x
+A 'listenv' vector with 3 elements ('a', '', 'c').
+</code></pre>
+
+<p>Just as for lists, a list environment is expanded with <code>NULL</code> elements whenever a new element is added that is beyond the current length plus one, e.g.</p>
+
+<pre><code class="r">> x[[5]] <- 5
+> x
+A 'listenv' vector with 5 elements ('a', '', 'c', '', '').
+> x[[4]]
+NULL
+</code></pre>
+
+<p>As with lists, the above list environment can also be created from the start, e.g.</p>
+
+<pre><code class="r">> x <- listenv(a = 1, 3, c = 4, NULL, 5)
+> x
+A 'listenv' vector with 5 elements ('a', '', 'c', '', '').
+</code></pre>
+
+<p>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.</p>
+
+<pre><code class="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
+</code></pre>
+
+<p>If increased, new elements are populated with unnamed elements of <code>NULL</code>, e.g.</p>
+
+<pre><code class="r">> length(x) <- 4
+> x
+A 'listenv' vector with 4 elements ('a', '', '', '').
+> x[[3]]
+NULL
+> x[[4]]
+NULL
+</code></pre>
+
+<p>To allocate an “empty” list environment (with all <code>NULL</code>:s) of a given length, do</p>
+
+<pre><code class="r">> x <- listenv()
+> length(x) <- 4
+> x
+A 'listenv' vector with 4 unnamed elements.
+</code></pre>
+
+<p><em>Note</em>: Unfortunately, it is <em>not</em> possible to use <code>x <- vector("listenv", length=4)</code>; that construct is only supported for the basic data types.</p>
+
+<h2>Iterating over elements</h2>
+
+<h3>Iterating over elements by names</h3>
+
+<p>Analogously to lists and plain environments, it is possible to iterate over elements of list environments by the element names.  For example,</p>
+
+<pre><code class="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
+</code></pre>
+
+<h3>Iterating over elements by indices</h3>
+
+<p>Analogously to lists, but contrary to plain environments, it is also possible to iterate over elements by their indices.  For example,</p>
+
+<pre><code class="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
+</code></pre>
+
+<h2>Coercion to and from list environments</h2>
+
+<h3>Coercing to lists and vectors</h3>
+
+<p>Coercing a list environment to a list:</p>
+
+<pre><code class="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"
+</code></pre>
+
+<p>Coercing a list to a list environment:</p>
+
+<pre><code class="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
+</code></pre>
+
+<p>Unlisting:</p>
+
+<pre><code class="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
+</code></pre>
+
+<h2>Multi-dimensional list environments</h2>
+
+<p>Analogously to lists, and contrary to plain environments, list environments can have dimensions with corresponding names.  For example,</p>
+
+<pre><code class="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 unnamed elements arranged in 2x3 rows ('a', 'b') and columns ('A', 'B', 'C').
+</code></pre>
+
+<p>An easy way to quickly get an overview is to coerce to a list, e.g.</p>
+
+<pre><code class="r">> as.list(x)
+  A B C
+a 1 3 5
+b 2 4 6
+</code></pre>
+
+<p>Individual elements of a list environment can be accessed using standard subsetting syntax, e.g.</p>
+
+<pre><code class="r">> x[["a", "B"]]
+[1] 3
+> x[[1, 2]]
+[1] 3
+> x[[1, "B"]]
+[1] 3
+</code></pre>
+
+<p>We can assign individual elements similarly, e.g.</p>
+
+<pre><code class="r">> x[["b", "B"]] <- -x[["b", "B"]]
+> as.list(x)
+  A B  C
+a 1 3  5
+b 2 -4 6
+</code></pre>
+
+<p>We can also assign multiple elements through dimensional subsetting, e.g.</p>
+
+<pre><code class="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
+</code></pre>
+
+<p>Concurrently with dimensional names it is possible to have names of the invidual elements just as for list environments without dimensions.  For example,</p>
+
+<pre><code class="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 
+</code></pre>
+
+<p>Contrary to lists, element names are preserved also with multi-dimensional subsetting, e.g.</p>
+
+<pre><code class="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').
+</code></pre>
+
+<p>Note, whenever dimensions are set using <code>dim(x) <- dims</code> both the dimensional names and the element names are removed, e.g.</p>
+
+<pre><code class="r">> dim(x) <- NULL
+> names(x)
+NULL
+</code></pre>
+
+<p>This behavior is by design, cf. <code>help("dim", package="base")</code>.</p>
+
+<h3>Limitations</h3>
+
+<p>The current implementation does <em>not</em> support <em>dimensional subsetting</em> of more than one element.  For instance, <code>x[1,]</code> is not supported by this version.</p>
+
+<h2>Important about environments</h2>
+
+<p>List environments are as their name suggests <em>environments</em>.  Whenever working with environments in R, it is important to understand that <em>environments are mutable</em> whereas all other of the basic data types in R are immutable.  For example, consider the following function that assigns zero to element <code>a</code> of object <code>x</code>:</p>
+
+<pre><code class="r">> setA <- function(x) {
++     x$a <- 0
++     x
++ }
+</code></pre>
+
+<p>If we pass a regular list to this function,</p>
+
+<pre><code class="r">> x <- list(a = 1)
+> y <- setA(x)
+> x$a
+[1] 1
+> y$a
+[1] 0
+</code></pre>
+
+<p>we see that <code>x</code> is unaffected by the assignment.  This is because <em>lists are immutable</em> in R.  However, if we pass an environment instead,</p>
+
+<pre><code class="r">> x <- new.env()
+> x$a <- 1
+> y <- setA(x)
+> x$a
+[1] 0
+> y$a
+[1] 0
+</code></pre>
+
+<p>we find that <code>x</code> was affected by the assignment.  This is because <em>environments are mutable</em> in R.  Since list environments inherits from environments, this also goes for them, e.g.</p>
+
+<pre><code class="r">> x <- listenv(a = 1)
+> y <- setA(x)
+> x$a
+[1] 0
+> y$a
+[1] 0
+</code></pre>
+
+<p>What is also important to understand is that it is not just the <em>content</em> of an environment that is mutable but also its <em>attributes</em>.  For example,</p>
+
+<pre><code class="r">> x <- listenv(a = 1)
+> y <- x
+> attr(y, "foo") <- "Hello!"
+> attr(x, "foo")
+[1] "Hello!"
+</code></pre>
+
+<hr/>
+
+<p>Copyright Henrik Bengtsson, 2015</p>
+
+</body>
+
+</html>
diff --git a/inst/doc/listenv.md.rsp b/inst/doc/listenv.md.rsp
new file mode 100644
index 0000000..c5e1de6
--- /dev/null
+++ b/inst/doc/listenv.md.rsp
@@ -0,0 +1,360 @@
+<%@meta language="R-vignette" content="--------------------------------
+%\VignetteIndexEntry{List Environments}
+%\VignetteAuthor{Henrik Bengtsson}
+%\VignetteKeyword{R}
+%\VignetteKeyword{package}
+%\VignetteKeyword{vignette}
+%\VignetteKeyword{listenv}
+%\VignetteEngine{R.rsp::rsp}
+%\VignetteTangle{FALSE}
+--------------------------------------------------------------------"%>
+<%
+R.utils::use("R.utils")
+use("listenv")
+options("withCapture/newline"=FALSE)
+%>
+# <%@meta name="title"%>
+
+_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  |              |
+| Indexed elements, e.g. `x[[4]]`                                        |              yes  |   yes  |              |
+| Dimensions, e.g. `dim(x)`                                              |              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]`, `[2:1,,3]`   |              yes  |   yes  |              |
+| Multi-element subsetting preserves element names                       |              yes  |        |              |
+| Removing element by assigning NULL, e.g. `x$c <- NULL`                 |              yes  |   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,
+```r
+<%=withCapture({
+x <- listenv(a=1, b=2, c="hello")
+x
+
+length(x)
+names(x)
+x$a
+x[[3]] <- toupper(x[[3]])
+x$c
+
+y <- x
+y$d <- y$a + y[["b"]]
+names(y)[2] <- "a"
+y$a
+y
+identical(y, x)
+
+for (ii in seq_along(x)) {
+  cat(sprintf("Element %d (%s): %s\n", ii, sQuote(names(x)[ii]), x[[ii]]))
+}
+<%---
+get(map(x)["b"], envir=x)
+assign(map(x)["b"], 3, envir=x)
+---%>
+
+x[c(1,3)] <- list(2, "Hello world!")
+
+x
+y <- as.list(x)
+str(y)
+z <- as.listenv(y)
+z
+identical(z, x)
+all.equal(z, x)
+})%>
+```
+
+## Creating list environments
+List environments are created similarly to lists but also similarly to environments.  To create an empty list environment, use
+```r
+<%=withCapture({
+x <- listenv()
+x
+})%>
+```
+This can later can be populated using named assignments,
+```r
+<%=withCapture({
+x$a <- 1
+x
+})%>
+```
+comparable to how both lists and environments work.  Similarly to lists, they can also be populated using indices, e.g.
+```r
+<%=withCapture({
+x[[2]] <- 2
+x$c <- 3
+x
+})%>
+```
+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
+<%=withCapture({
+x[[5]] <- 5
+x
+x[[4]]
+})%>
+```
+
+As with lists, the above list environment can also be created from the start, e.g.
+```r
+<%=withCapture({
+x <- listenv(a=1, 3, c=4, NULL, 5)
+x
+})%>
+```
+
+
+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
+<%=withCapture({
+x
+length(x) <- 2
+x
+x[[1]]
+x[[2]]
+})%>
+```
+If increased, new elements are populated with unnamed elements of `NULL`, e.g.
+```r
+<%=withCapture({
+length(x) <- 4
+x
+x[[3]]
+x[[4]]
+})%>
+```
+
+To allocate an "empty" list environment (with all `NULL`:s) of a given length, do
+```r
+<%=withCapture({
+x <- listenv()
+length(x) <- 4
+x
+})%>
+```
+_Note_: Unfortunately, it is _not_ possible to use `x <- vector("listenv", length=4)`; that construct is only supported for the basic data types.
+
+
+
+## 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
+<%=withCapture({
+x <- listenv(a=1, b=2, c=3)
+for (name in names(x)) {
+  cat(sprintf("Element %s: %s\n", sQuote(name), x[[name]]))
+}
+})%>
+```
+
+### 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
+<%=withCapture({
+x <- listenv(a=1, b=2, c=3)
+for (ii in seq_along(x)) {
+  cat(sprintf("Element %d: %s\n", ii, x[[ii]]))
+}
+})%>
+```
+
+
+## Coercion to and from list environments
+
+### Coercing to lists and vectors
+
+Coercing a list environment to a list:
+```r
+<%=withCapture({
+x <- listenv(a=2, b=3, c="hello")
+x
+y <- as.list(x)
+str(y)
+})%>
+```
+
+Coercing a list to a list environment:
+```r
+<%=withCapture({
+z <- as.listenv(y)
+z
+identical(z, x)
+all.equal(z, x)
+})%>
+```
+
+Unlisting:
+```r
+<%=withCapture({
+unlist(x)
+unlist(x[-3])
+unlist(x[1:2], use.names=FALSE)
+})%>
+```
+
+
+## Multi-dimensional list environments
+
+Analogously to lists, and contrary to plain environments, list environments can have dimensions with corresponding names.  For example,
+```r
+<%=withCapture({
+x <- as.listenv(1:6)
+dim(x) <- c(2,3)
+dimnames(x) <- list(c("a", "b"), c("A", "B","C"))
+x
+})%>
+```
+An easy way to quickly get an overview is to coerce to a list, e.g.
+```r
+<%=withCapture({
+as.list(x)
+})%>
+```
+Individual elements of a list environment can be accessed using standard subsetting syntax, e.g.
+```r
+<%=withCapture({
+x[["a", "B"]]
+x[[1, 2]]
+x[[1, "B"]]
+})%>
+```
+We can assign individual elements similarly, e.g.
+```r
+<%=withCapture({
+x[["b", "B"]] <- -x[["b", "B"]]
+as.list(x)
+})%>
+```
+We can also assign multiple elements through dimensional subsetting, e.g.
+```r
+<%=withCapture({
+x[2,-1] <- 98:99
+as.list(x)
+x["a",c(1,3)] <- list(97, "foo")
+as.list(x)
+x[] <- 1:6
+as.list(x)
+})%>
+```
+
+
+Concurrently with dimensional names it is possible to have names of the invidual elements just as for list environments without dimensions.  For example,
+```r
+<%=withCapture({
+names(x) <- letters[seq_along(x)]
+x
+x[["a"]]
+x[["f"]]
+x[c("a", "f")]
+unlist(x)
+})%>
+```
+Contrary to lists, element names are preserved also with multi-dimensional subsetting, e.g.
+```r
+<%=withCapture({
+x[1,2]
+x[1,2,drop=FALSE]
+x[1:2,2:1]
+x[2,]
+x[2,,drop=FALSE]
+x["b",-2,drop=FALSE]
+})%>
+```
+
+
+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")`.
+<%---
+Because of this, the listenv package provides the `undim()` function, which removes the dimensions but preserves the names, e.g.
+```r
+<%=withCapture({
+x <- undim(x)
+names(x)
+})%>
+```
+_Warning_: Since list environments _and their attributes_ are mutable, calling
+```r
+undim(x)
+```
+will have the same effect as
+```r
+x <- undim(x)
+```
+That is, the dimension attributes of `x` will be changed.  The reason for this is explained in Section 'Important about environments' above.
+---%>
+
+### Limitations
+The current implementation does _not_ support _dimensional subsetting_ of more than one element.  For instance, `x[1,]` is not supported by this version.
+
+
+
+## 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
+<%=withCapture({
+setA <- function(x) {
+  x$a <- 0
+  x
+}
+})%>
+```
+If we pass a regular list to this function,
+```r
+<%=withCapture({
+x <- list(a=1)
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+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
+<%=withCapture({
+x <- new.env()
+x$a <- 1
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+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.
+```r
+<%=withCapture({
+x <- listenv(a=1)
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+
+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
+<%=withCapture({
+x <- listenv(a=1)
+y <- x
+attr(y, "foo") <- "Hello!"
+attr(x, "foo")
+})%>
+```
+
+
+[listenv]: http://cran.r-project.org/package=listenv
+
+---
+Copyright Henrik Bengtsson, 2015
diff --git a/man/as.list.listenv.Rd b/man/as.list.listenv.Rd
new file mode 100644
index 0000000..c7aea62
--- /dev/null
+++ b/man/as.list.listenv.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{as.list.listenv}
+\alias{as.list.listenv}
+\title{List representation of a list environment}
+\usage{
+\method{as.list}{listenv}(x, all.names = TRUE, sorted = FALSE, ...)
+}
+\arguments{
+\item{x}{A list environment.}
+
+\item{all.names}{If \code{TRUE}, variable names starting with
+a period are included, otherwise not.}
+
+\item{sorted}{If \code{TRUE}, elements are ordered by their names
+before being compared, otherwise not.}
+
+\item{...}{Not used.}
+}
+\value{
+A list.
+}
+\description{
+List representation of a list environment
+}
+\keyword{internal}
+
diff --git a/man/cash-.listenv.Rd b/man/cash-.listenv.Rd
new file mode 100644
index 0000000..2d415a5
--- /dev/null
+++ b/man/cash-.listenv.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{$.listenv}
+\alias{$.listenv}
+\alias{[[.listenv}
+\title{Get elements of list environment}
+\usage{
+\method{$}{listenv}(x, name)
+}
+\arguments{
+\item{x}{A list environment.}
+
+\item{name}{The name or index of the element to retrieve.}
+}
+\value{
+The value of an element or NULL if the element does not exist
+}
+\description{
+Get elements of list environment
+}
+\keyword{internal}
+
diff --git a/man/cash-set-.listenv.Rd b/man/cash-set-.listenv.Rd
new file mode 100644
index 0000000..1b09ca3
--- /dev/null
+++ b/man/cash-set-.listenv.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{$<-.listenv}
+\alias{$<-.listenv}
+\alias{[[<-.listenv}
+\title{Set an element of list environment}
+\usage{
+\method{$}{listenv}(x, name) <- value
+}
+\arguments{
+\item{x}{A list environment.}
+
+\item{name}{Name or index of element}
+
+\item{value}{The value to assign to the element}
+}
+\description{
+Set an element of list environment
+}
+\keyword{internal}
+
diff --git a/man/get_variable.Rd b/man/get_variable.Rd
new file mode 100644
index 0000000..7679be4
--- /dev/null
+++ b/man/get_variable.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_variable.R
+\name{get_variable}
+\alias{get_variable}
+\alias{get_variable.listenv}
+\title{Get name of variable for a specific element of list environment}
+\usage{
+get_variable(...)
+}
+\arguments{
+\item{x}{A list environment.}
+
+\item{name}{The name or index of element of interest.}
+
+\item{mustExist}{If TRUE, an error is generated if \code{name}
+does not exist.}
+
+\item{create}{If TRUE, element \code{name} is created if missing.}
+}
+\value{
+The name of the underlying variable
+}
+\description{
+Get name of variable for a specific element of list environment
+}
+\keyword{internal}
+
diff --git a/man/length.listenv.Rd b/man/length.listenv.Rd
new file mode 100644
index 0000000..243bb5d
--- /dev/null
+++ b/man/length.listenv.Rd
@@ -0,0 +1,16 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{length.listenv}
+\alias{length.listenv}
+\title{Number of elements in list environment}
+\usage{
+\method{length}{listenv}(x)
+}
+\arguments{
+\item{x}{A list environment.}
+}
+\description{
+Number of elements in list environment
+}
+\keyword{internal}
+
diff --git a/man/listenv.Rd b/man/listenv.Rd
new file mode 100644
index 0000000..8dbd844
--- /dev/null
+++ b/man/listenv.Rd
@@ -0,0 +1,33 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{listenv}
+\alias{as.listenv}
+\alias{listenv}
+\title{Create a list environment}
+\usage{
+listenv(...)
+
+as.listenv(...)
+}
+\arguments{
+\item{\dots}{(optional) Named and/or unnamed objects to be
+assigned to the list environment.}
+}
+\value{
+An environment of class `listenv`.
+}
+\description{
+Create a list environment
+}
+\examples{
+x <- listenv(c=2, a=3, d="hello")
+print(names(x))
+names(x)[2] <- "A"
+x$b <- 5:8
+
+y <- as.list(x)
+str(y)
+
+z <- as.listenv(y)
+}
+
diff --git a/man/map.Rd b/man/map.Rd
new file mode 100644
index 0000000..cc6d24a
--- /dev/null
+++ b/man/map.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{map}
+\alias{map}
+\alias{map.listenv}
+\title{Variable name map for elements of list environment}
+\usage{
+map(x, ...)
+}
+\arguments{
+\item{x}{A list environment.}
+}
+\value{
+The a named character vector
+}
+\description{
+Variable name map for elements of list environment
+}
+\keyword{internal}
+
diff --git a/man/names.listenv.Rd b/man/names.listenv.Rd
new file mode 100644
index 0000000..72620f6
--- /dev/null
+++ b/man/names.listenv.Rd
@@ -0,0 +1,17 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/listenv.R
+\name{names.listenv}
+\alias{names.listenv}
+\alias{names<-.listenv}
+\title{Names of elements in list environment}
+\usage{
+\method{names}{listenv}(x)
+}
+\arguments{
+\item{x}{A list environment.}
+}
+\description{
+Names of elements in list environment
+}
+\keyword{internal}
+
diff --git a/man/parse_env_subset.Rd b/man/parse_env_subset.Rd
new file mode 100644
index 0000000..43d2890
--- /dev/null
+++ b/man/parse_env_subset.Rd
@@ -0,0 +1,24 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/parse_env_subset.R
+\name{parse_env_subset}
+\alias{parse_env_subset}
+\title{Helper function to infer target from expression and environment}
+\usage{
+parse_env_subset(expr, envir = parent.frame(), substitute = TRUE)
+}
+\arguments{
+\item{expr}{An expression.}
+
+\item{envir}{An environment.}
+
+\item{substitute}{If TRUE, then the expression is
+\code{substitute()}:ed, otherwise not.}
+}
+\value{
+A named list.
+}
+\description{
+Helper function to infer target from expression and environment
+}
+\keyword{internal}
+
diff --git a/man/undim.Rd b/man/undim.Rd
new file mode 100644
index 0000000..33ab05e
--- /dev/null
+++ b/man/undim.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/undim.R
+\name{undim}
+\alias{undim}
+\alias{undim.default}
+\title{Removes the dimension of an object}
+\usage{
+undim(x, ...)
+}
+\arguments{
+\item{x}{An object with or without dimensions}
+
+\item{...}{Not used.}
+}
+\value{
+The object with the dimension attribute removed.
+}
+\description{
+Removes the dimension of an object
+}
+\details{
+This function does \code{attr(x, "dim") <- NULL}, which
+automatically also does \code{attr(x, "dimnames") <- NULL}.
+However, other attributes such as names attributes are preserved,
+which is not the case if one do \code{dim(x) <- NULL}.
+}
+
diff --git a/tests/as.listenv.R b/tests/as.listenv.R
new file mode 100644
index 0000000..2bc9b92
--- /dev/null
+++ b/tests/as.listenv.R
@@ -0,0 +1,33 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+oopts <- options(warn=1)
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Single-element assignments and subsetting
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- list(a=1, b=2, c=3)
+str(x)
+y <- as.listenv(x)
+print(y)
+stopifnot(identical(as.list(y), x))
+z <- as.listenv(y)
+stopifnot(identical(as.list(y), as.list(z)))
+
+e <- new.env()
+e$a <- 1
+e$b <- 2
+e$c <- 3
+y <- as.listenv(e)
+print(y)
+stopifnot(identical(as.list(y), as.list(e)))
+
+x <- c(a=1, b=2, c=3)
+y <- as.listenv(x)
+print(y)
+stopifnot(identical(as.list(y), as.list(x)))
+
+## Cleanup
+options(oopts)
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/get_variable,dimensions.R b/tests/get_variable,dimensions.R
new file mode 100644
index 0000000..a08f603
--- /dev/null
+++ b/tests/get_variable,dimensions.R
@@ -0,0 +1,38 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+oopts <- options(warn=1)
+map <- listenv:::map
+
+message("* get_variable() - multi-dimensional list environments ...")
+
+x <- listenv()
+length(x) <- 6
+dim(x) <- c(2,3)
+
+for (ii in seq_along(x)) {
+  stopifnot(is.null(x[[ii]]))
+  idx <- arrayInd(ii, .dim=dim(x))
+  stopifnot(is.null(x[[idx[1],idx[2]]]))
+  varV <- get_variable(x, ii, create=FALSE)
+  varA <- get_variable(x, idx, create=FALSE)
+  stopifnot(identical(varA, varV))
+}
+
+x[1:6] <- 1:6
+for (ii in seq_along(x)) {
+  stopifnot(identical(x[[ii]], ii))
+  idx <- arrayInd(ii, .dim=dim(x))
+  stopifnot(identical(x[[idx[1],idx[2]]], ii))
+
+  varV <- get_variable(x, ii)
+  varA <- get_variable(x, idx)
+  stopifnot(identical(varA, varV))
+}
+
+
+message("* get_variable() - multi-dimensional list environments ... DONE")
+
+## Cleanup
+options(oopts)
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/get_variable.R b/tests/get_variable.R
new file mode 100644
index 0000000..625a331
--- /dev/null
+++ b/tests/get_variable.R
@@ -0,0 +1,100 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+oopts <- options(warn=1)
+map <- listenv:::map
+
+x <- listenv()
+length(x) <- 3L
+names(x) <- c("a", "b", "c")
+stopifnot(length(x) == 3L)
+print(map(x))
+
+var <- get_variable(x, "a")
+stopifnot(!is.na(var))
+stopifnot(length(x) == 3L)
+print(map(x))
+
+var <- get_variable(x, "b")
+stopifnot(!is.na(var))
+stopifnot(length(x) == 3L)
+print(map(x))
+
+var <- get_variable(x, "c")
+stopifnot(!is.na(var))
+stopifnot(length(x) == 3L)
+print(map(x))
+
+var <- get_variable(x, "d")
+stopifnot(!is.na(var))
+stopifnot(length(x) == 4L)
+print(map(x))
+
+var <- get_variable(x, 4L)
+stopifnot(!is.na(var))
+stopifnot(length(x) == 4L)
+print(map(x))
+
+x$b <- 2
+var <- get_variable(x, "b")
+stopifnot(!is.na(var))
+stopifnot(length(x) == 4L)
+print(map(x))
+
+var <- get_variable(x, length(x) + 1L)
+stopifnot(length(x) == 5L)
+print(names(x))
+print(map(x))
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Allocation
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+length(x) <- 3L
+print(x[[1]])
+print(x[[2]])
+print(x[[3]])
+
+## Out-of-bound subsetting
+res <- try(x[[0]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+## Out-of-bound subsetting
+res <- try(x[[4]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+print(get_variable(x, 1L, mustExist=FALSE))
+print(get_variable(x, 2L, mustExist=FALSE))
+print(get_variable(x, 3L, mustExist=FALSE))
+
+## Out-of-bound element
+res <- try(var <- get_variable(x, 0L, mustExist=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+## Out-of-bound element
+res <- try(var <- get_variable(x, length(x) + 1L, mustExist=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Exception handling
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+length(x) <- 3L
+names(x) <- c("a", "b", "c")
+
+## Non-existing element
+res <- try(var <- get_variable(x, "z", mustExist=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(var <- get_variable(x, c("a", "b")), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(var <- get_variable(x, 1+2i), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+
+
+## Cleanup
+options(oopts)
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/listenv,dimensions.R b/tests/listenv,dimensions.R
new file mode 100644
index 0000000..4c7fecb
--- /dev/null
+++ b/tests/listenv,dimensions.R
@@ -0,0 +1,303 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+oopts <- options(warn=1)
+
+
+message("* List environment and multiple dimensions ...")
+
+x <- listenv()
+dim(x) <- c(0,0)
+print(x)
+stopifnot(length(x) == 0)
+
+x <- listenv(a=1)
+stopifnot(identical(names(x), "a"))
+dim(x) <- c(1,1)
+print(x)
+stopifnot(length(x) == 1)
+stopifnot(is.null(dimnames(x)))
+stopifnot(is.null(names(x)))
+
+x0 <- as.list(1:6)
+x <- as.listenv(x0)
+print(x)
+stopifnot(is.null(dim(x)))
+stopifnot(is.null(dimnames(x)))
+y <- as.list(x)
+stopifnot(identical(y, x0))
+z <- as.listenv(y)
+stopifnot(all.equal(z, x))
+
+
+message("* dim(x) and dimnames(x) ...")
+dims <- list(2:3, 2:4)
+for (kk in seq_along(dims)) {
+  dim <- dims[[kk]]
+  dimnames <- lapply(dim, FUN=function(n) letters[seq_len(n)])
+  names <- letters[seq_len(prod(dim))]
+  str(list(dim=dim, dimnames=dimnames, names=names))
+
+  n <- prod(dim)
+  values <- seq_len(n)
+
+  x0 <- as.list(values)
+  x <- as.listenv(values)
+  print(x)
+  stopifnot(identical(dim(x), dim(x0)))
+  y <- as.list(x)
+  stopifnot(identical(y, x0))
+  z <- as.listenv(y)
+  stopifnot(all.equal(z, x))
+
+  dim(x0) <- dim
+  dim(x) <- dim
+  print(x)
+  stopifnot(identical(dim(x), dim(x0)))
+  stopifnot(is.null(dimnames(x)))
+  stopifnot(is.null(names(x)))
+  names(x0) <- names
+  names(x) <- names
+  y <- as.list(x)
+  stopifnot(identical(y, x0))
+  z <- as.listenv(y)
+  stopifnot(all.equal(z, x))
+
+  excls <- c(list(NULL), as.list(seq_along(dimnames)), list(seq_along(dimnames)))
+  for (ll in seq_along(excls)) {
+    excl <- excls[[ll]]
+    dimnamesT <- dimnames
+    dimnamesT[excl] <- list(NULL)
+    dimnames(x0) <- dimnamesT
+    dimnames(x) <- dimnamesT
+    print(x)
+    stopifnot(identical(dim(x), dim(x0)))
+    stopifnot(identical(dimnames(x), dimnames(x0)))
+    stopifnot(identical(names(x), names))
+    y <- as.list(x)
+    stopifnot(identical(y, x0))
+    z <- as.listenv(y)
+    stopifnot(all.equal(z, x))
+  } ## for (ll ...)
+} ## for (kk ...)
+
+
+# Assign names
+x <- as.listenv(1:6)
+dim(x) <- c(2,3)
+dimnames(x) <- lapply(dim(x), FUN=function(n) letters[seq_len(n)])
+names(x) <- letters[seq_along(x)]
+print(x)
+stopifnot(!is.null(dim(x)))
+stopifnot(!is.null(dimnames(x)))
+stopifnot(!is.null(names(x)))
+stopifnot(x[["b"]] == 2L)
+stopifnot(x[["a", "b"]] == 3L)
+
+## Extract single element
+message("* y <- x[[i,j]] and z <- x[i,j] ...")
+dim(x) <- c(2,3)
+dimnames(x) <- list(c("a", "b"), NULL)
+
+y <- x[[3]]
+stopifnot(identical(y, 3L))
+z <- x[3]
+stopifnot(identical(z[[1]], y))
+
+y <- x[[1,1]]
+stopifnot(identical(y, x[[1]]))
+z <- x[1,1]
+stopifnot(identical(z[[1]], y))
+
+y <- x[[2,3]]
+stopifnot(identical(y, x[[6]]))
+z <- x[2,3]
+stopifnot(identical(z[[1]], y))
+
+y <- x[["a",3]]
+stopifnot(identical(y, x[[1,3]]))
+stopifnot(identical(y, x[[5]]))
+z <- x["a",3]
+stopifnot(identical(z[[1]], y))
+
+
+y <- x[[1,c(FALSE,FALSE,TRUE)]]
+stopifnot(identical(y, x[[1,3]]))
+stopifnot(identical(y, x[[5]]))
+z <- x[1,c(FALSE,FALSE,TRUE)]
+stopifnot(identical(z[[1]], y))
+
+
+message("* x[[i,j]] <- value ...")
+## Assign single element
+x[[3]] <- -x[[3]]
+stopifnot(identical(x[[3]], -3L))
+
+x[[1,1]] <- -x[[1,1]]
+stopifnot(identical(x[[1]], -1L))
+
+x[[2,3]] <- -x[[2,3]]
+stopifnot(identical(x[[6]], -6L))
+
+x[["a",3]] <- -x[["a",3]]
+stopifnot(identical(x[[1,3]], -5L))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Multi-element subsetting
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("* x[i], x[i,j] ...")
+x <- as.listenv(1:24)
+dim(x) <- c(2,3,4)
+names(x) <- letters[seq_along(x)]
+x[2] <- list(NULL)
+print(x)
+
+y <- x[]
+print(y)
+stopifnot(length(y) == length(x))
+stopifnot(all.equal(y, x))
+stopifnot(!identical(y, x))
+stopifnot(all.equal(as.list(y), as.list(x)[]))
+
+y <- x[1]
+print(y)
+stopifnot(all.equal(as.list(y), as.list(x)[1]))
+
+y <- x[2:3]
+print(y)
+stopifnot(all.equal(as.list(y), as.list(x)[2:3]))
+
+y <- x[-1]
+print(y)
+stopifnot(all.equal(as.list(y), as.list(x)[-1]))
+
+y <- x[1:2,1:3,1:4]
+print(y)
+stopifnot(all.equal(dim(y), dim(x)))
+stopifnot(all.equal(y, x))
+stopifnot(all.equal(unlist(y), unlist(x)))
+stopifnot(all.equal(as.list(y), as.list(x)[1:2,1:3,1:4], check.attributes=FALSE))
+
+y <- x[0,0,0]
+print(y)
+stopifnot(length(y) == 0)
+stopifnot(all.equal(dim(y), c(0,0,0)))
+stopifnot(all.equal(y, as.list(x)[0,0,0]))
+
+y <- x[0,,]
+print(y)
+stopifnot(length(y) == 0)
+stopifnot(all.equal(dim(y), c(0,dim(x)[-1])))
+stopifnot(all.equal(y, as.list(x)[0,,]))
+
+y <- x[2,1,,drop=FALSE]
+print(y)
+stopifnot(all.equal(dim(y), c(1,1,dim(x)[3])))
+stopifnot(all.equal(as.list(y), as.list(x)[2,1,,drop=FALSE], check.attributes=FALSE))
+
+y <- x[2,1,,drop=TRUE]
+print(y)
+stopifnot(is.null(dim(y)))
+stopifnot(all.equal(as.list(y), as.list(x)[2,1,,drop=TRUE], check.attributes=FALSE))
+
+y <- x[2,1,]
+print(y)
+stopifnot(is.null(dim(y)))
+stopifnot(all.equal(as.list(y), as.list(x)[2,1,], check.attributes=FALSE))
+
+y <- x[-1,,c(3,3,1)]
+print(y)
+stopifnot(all.equal(as.list(y), as.list(x)[-1,,c(3,3,1)], check.attributes=FALSE))
+
+message("* x[i], x[i,j] ... DONE")
+
+
+message("* x[i] <- value, x[i,j] <- value ...")
+dim <- c(2,3)
+n <- prod(dim)
+names <- letters[seq_len(n)]
+
+x0 <- as.list(1:n)
+dim(x0) <- dim
+names(x0) <- names
+
+x <- as.listenv(1:n)
+dim(x) <- dim
+names(x) <- names
+
+x0[] <- 6:1
+x[] <- 6:1
+stopifnot(all(unlist(x) == unlist(x0)))
+
+x0[1,] <- 1:3
+x[1,] <- 1:3
+stopifnot(all(unlist(x) == unlist(x0)))
+
+x0[,-2] <- 1:2
+x[,-2] <- 1:2
+stopifnot(all(unlist(x) == unlist(x0)))
+
+message("* x[i] <- value, x[i,j] <- value ... DONE")
+
+
+message("* Exceptions ...")
+x <- listenv()
+res <- try(dim(x) <- c(2,3), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+length(x) <- 6
+dim(x) <- c(2,3)
+
+res <- try(x[[3,3]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[3,3], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[c(-1,1),3], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[c(TRUE, TRUE, TRUE),], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(dimnames(x) <- NA, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(dimnames(x) <- list("a", "b", "c"), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(dimnames(x) <- list("a", NULL), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+dimnames(x) <- list(c("a", "b"), NULL)
+
+
+message("* Changing dim(x) and dimnames(x) ...")
+x <- listenv()
+x[1:12] <- 1:12
+dim(x) <- c(2,2,3)
+dimnames(x) <- list(c("a", "b"), NULL, NULL)
+print(x)
+stopifnot(identical(dim(x), c(2L,2L,3L)))
+stopifnot(identical(dimnames(x), list(c("a", "b"), NULL, NULL)))
+x[[2,1,2]] <- -x[[2,1,2]]
+y <- unlist(x)
+print(y)
+
+dim(x) <- c(4,3)
+print(x)
+stopifnot(identical(dim(x), c(4L,3L)))
+stopifnot(is.null(dimnames(x)))
+x[[2,2]] <- -x[[2,2]]
+y <- unlist(x)
+print(y)
+stopifnot(identical(y, 1:12))
+
+
+message("* List environment and multiple dimensions ... DONE")
+
+
+## Cleanup
+options(oopts)
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/listenv.R b/tests/listenv.R
new file mode 100644
index 0000000..eeef4e0
--- /dev/null
+++ b/tests/listenv.R
@@ -0,0 +1,692 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+oopts <- options(warn=1)
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Allocation
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+print(x)
+stopifnot(length(x) == 0)
+stopifnot(is.null(names(x)))
+
+x <- listenv(a=1)
+print(x)
+stopifnot(length(x) == 1)
+stopifnot(identical(names(x), c("a")))
+stopifnot(identical(x$a, 1))
+
+x <- listenv(a=1, b=2:3)
+print(x)
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(x$a, 1), identical(x$b, 2:3))
+
+
+x <- listenv(b=2:3, .a=1)
+print(x)
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("b", ".a")))
+stopifnot(identical(x$.a, 1), identical(x$b, 2:3))
+
+
+x <- listenv(length=3, a=1)
+print(x)
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("length", "a")))
+stopifnot(identical(x$length, 3), identical(x$a, 1))
+
+
+withCallingHandlers({
+  x <- listenv(length=3)
+}, warning = function(warn) {
+  cat("WARNING:", warn$message)
+  invokeRestart("muffleWarning")
+})
+print(x)
+stopifnot(length(x) == 3)
+stopifnot(is.null(names(x)))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Single-element assignments and subsetting
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 0)
+
+x$a <- 1
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 1)
+stopifnot(identical(names(x), c("a")))
+stopifnot(identical(x$a, 1), is.null(x$b))
+
+x$b <- 2
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(x$b, 2))
+
+x$a <- 0
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(x[["a"]], 0))
+
+x$"a" <- 1
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(x$a, 1))
+
+x[["a"]] <- 0
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+
+
+key <- "b"
+x[[key]] <- 3
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(x$b, 3), identical(x[["b"]], 3), identical(x[[key]], 3))
+
+x[[3]] <- 3.14
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "")))
+stopifnot(identical(x[[3]], 3.14))
+
+names(x) <- c("a", "b", "c")
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "c")))
+stopifnot(identical(x[[3]], 3.14), identical(x[["c"]], 3.14), identical(x$c, 3.14))
+
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Multi-element subsetting
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Assert than no false names are introduced
+x <- listenv()
+x[1:3] <- list(1, NULL, 3)
+print(x)
+stopifnot(is.null(names(x)))
+
+y <- x[]
+print(y)
+stopifnot(length(y) == length(x))
+stopifnot(all.equal(y, x))
+stopifnot(!identical(y, x))
+stopifnot(is.null(names(y)))
+
+y <- x[1]
+print(y)
+stopifnot(is.null(names(y)))
+
+y <- x[2:3]
+print(y)
+stopifnot(is.null(names(y)))
+
+y <- x[-1]
+print(y)
+stopifnot(is.null(names(y)))
+
+x[c('c', '.a', 'b')] <- list(NULL, 3, 1)
+print(x)
+stopifnot(identical(names(x), c("", "", "", "c", ".a", "b")))
+
+y <- as.list(x)
+str(y)
+stopifnot(identical(names(y), c("", "", "", "c", ".a", "b")))
+
+y <- as.list(x, all.names=FALSE)
+str(y)
+stopifnot(identical(names(y), c("", "", "", "c", "b")))
+
+y <- as.list(x, sorted=TRUE)
+str(y)
+stopifnot(identical(names(y), c("", "", "", ".a", "b", "c")))
+
+y <- as.list(x, all.names=FALSE, sorted=TRUE)
+str(y)
+stopifnot(identical(names(y), c("", "", "", "b", "c")))
+
+
+x <- listenv()
+x[c('a', 'b', 'c')] <- list(1, NULL, 3)
+
+y <- x[NULL]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list()))
+
+y <- x[integer(0L)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list()))
+
+y <- x["a"]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1)))
+
+y <- x[c("a","c")]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1, c=3)))
+
+y <- x[c("c","a")]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(c=3, a=1)))
+
+y <- x[c(1,3)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1, c=3)))
+
+y <- x[-2]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1, c=3)))
+
+y <- x[-c(1,3)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(b=NULL)))
+
+y <- x[rep(1L, times=6L)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, rep(list(a=1), times=6L)))
+
+y <- x[1:10]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, c(as.list(x), rep(list(NULL), times=7L))))
+
+
+y <- x[c(TRUE, FALSE, TRUE)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1, c=3)))
+
+y <- x[c(TRUE, FALSE)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list(a=1, c=3)))
+
+y <- x[TRUE]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, as.list(x)))
+
+y <- x[FALSE]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, list()))
+
+y <- x[rep(TRUE, times=5L)]
+print(y)
+z <- as.list(y)
+print(z)
+stopifnot(identical(z, c(as.list(x), list(NULL), list(NULL))))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Local access
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv(a=1, b=2, c=3.14)
+
+y <- local({
+  x[[3]]
+})
+stopifnot(identical(y, 3.14))
+
+y <- local({
+  x[3]
+})
+stopifnot(identical(y[[1]], 3.14))
+
+y <- local({
+  ii <- 3
+  x[[ii]]
+})
+stopifnot(identical(y, 3.14))
+
+y <- local({
+  ii <- 3
+  x[ii]
+})
+stopifnot(identical(y[[1]], 3.14))
+
+
+local({
+  x[[3]] <- 42L
+})
+y <- x[[3]]
+stopifnot(identical(y, 42L))
+
+local({
+  x[3] <- 3.14
+})
+y <- x[[3]]
+stopifnot(identical(y, 3.14))
+
+local({
+  ii <- 3
+  x[ii] <- 42L
+})
+y <- x[[3]]
+stopifnot(identical(y, 42L))
+
+local({
+  ii <- 3
+  x[[ii]] <- 3.14
+})
+y <- x[[3]]
+stopifnot(identical(y, 3.14))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Removing elements
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x[["a"]] <- NULL
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("b", "c")))
+
+x[[3L]] <- NULL
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("b", "c")))
+
+x[[2L]] <- NULL
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 1)
+stopifnot(identical(names(x), c("b")))
+
+x$b <- NULL
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 0)
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Assigning NULL
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x[2L] <- list(NULL)
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("", "")))
+
+x['c'] <- list(NULL)
+print(x)
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("", "", "c")))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Assigning multiple elements at once
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+x[c('a', 'b', 'c')] <- 1:3
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "c")))
+stopifnot(identical(as.list(x), list(a=1L, b=2L, c=3L)))
+stopifnot(identical(unlist(x), c(a=1L, b=2L, c=3L)))
+
+x[] <- 3:1
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "c")))
+stopifnot(identical(as.list(x), list(a=3L, b=2L, c=1L)))
+
+x[c('c', 'b')] <- 2:3
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "c")))
+stopifnot(identical(as.list(x), list(a=3L, b=3L, c=2L)))
+
+x[c('a', 'c')] <- 1L
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "c")))
+stopifnot(identical(as.list(x), list(a=1L, b=3L, c=1L)))
+
+x[c('d', 'e')] <- 4:5
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 5)
+stopifnot(identical(names(x), c("a", "b", "c", "d", "e")))
+stopifnot(identical(as.list(x), list(a=1L, b=3L, c=1L, d=4L, e=5L)))
+
+
+x <- listenv()
+x[c('a', 'b')] <- 1:2
+x[c(TRUE,FALSE)] <- 2L
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(as.list(x), list(a=2L, b=2L)))
+
+x[c(TRUE)] <- 1L
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 2)
+stopifnot(identical(names(x), c("a", "b")))
+stopifnot(identical(as.list(x), list(a=1L, b=1L)))
+
+x[c(TRUE,FALSE,TRUE,FALSE)] <- 3L
+print(x)
+str(as.list(x))
+print(length(x))
+print(names(x))
+stopifnot(length(x) == 3)
+stopifnot(identical(names(x), c("a", "b", "")))
+stopifnot(identical(as.list(x), list(a=3L, b=1L, 3L)))
+
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Expanding
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+for (ii in 1:3) {
+  x[[ii]] <- letters[ii]
+  print(x[[ii]])
+}
+print(x)
+names(x) <- sprintf("item%d", seq_along(x))
+print(x)
+
+y <- as.list(x)
+str(y)
+stopifnot(identical(names(y), c("item1", "item2", "item3")))
+stopifnot(identical(y[[1]], "a"), identical(y[[2]], "b"), identical(y[[3]], "c"))
+x[[2]] <- "B"
+stopifnot(identical(x$item2, "B"))
+
+
+x <- listenv()
+x[[1]] <- { 1 }
+x[[3]] <- { "Hello world!" }
+stopifnot(length(x) == 3)
+stopifnot(identical(seq_along(x), seq_len(length(x))))
+print(x)
+names(x) <- c("a", "b", "c")
+print(x)
+x$b <- TRUE
+stopifnot(identical(x[[1]], 1))
+stopifnot(identical(x[[2]], TRUE))
+stopifnot(identical(x$b, TRUE))
+stopifnot(identical(x[["b"]], TRUE))
+y <- as.list(x)
+str(y)
+stopifnot(length(y) == 3)
+
+
+## Mixed names and indices
+x <- listenv()
+x$a <- 1
+x[[3]] <- 3
+print(names(x))
+stopifnot(identical(names(x), c("a", "", "")))
+
+# First element (should be named 'a')
+var <- get_variable(x, "a")
+stopifnot(var == "a")
+var <- get_variable(x, 1)
+stopifnot(var == "a")
+
+# Third element (should be a temporary name)
+var <- get_variable(x, 3)
+stopifnot(var != "c")
+names(x) <- c("a", "b", "c")
+var <- get_variable(x, 3)
+stopifnot(var != "c")
+var <- get_variable(x, "c")
+stopifnot(var != "c")
+
+## Second element (should become 'b', because it was never used
+#                  before it was "named" 'b')
+x$b <- 2
+var <- get_variable(x, 2)
+stopifnot(var == "b")
+var <- get_variable(x, "b")
+stopifnot(var == "b")
+
+
+## Names where as.integer(names(x)) are integers
+x <- listenv()
+x[["1"]] <- 1
+x[["3"]] <- 3
+print(names(x))
+stopifnot(identical(names(x), c("1", "3")))
+
+
+## Expand and shrink
+x <- listenv()
+stopifnot(length(x) == 0L)
+length(x) <- 3L
+stopifnot(length(x) == 3L)
+stopifnot(is.null(names(x)))
+
+names(x) <- c("a", "b", "c")
+x$a <- 2
+stopifnot(identical(x$a, 2))
+x[c("a", "c")] <- c(2,1)
+stopifnot(identical(x$a, 2), identical(x$c, 1))
+
+length(x) <- 4L
+stopifnot(length(x) == 4L)
+stopifnot(identical(names(x), c("a", "b", "c", "")))
+
+length(x) <- 1L
+stopifnot(length(x) == 1L)
+stopifnot(identical(names(x), c("a")))
+stopifnot(identical(x$a, 2))
+
+length(x) <- 0L
+stopifnot(length(x) == 0L)
+stopifnot(length(names(x)) == 0) ## Actually, character(0), cf. lists
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Flatten
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+for (recursive in c(FALSE, TRUE)) {
+  x <- list(); x$a <- list(B=1:3); x$b <- list(C=1:3, D=4:5)
+  y1 <- unlist(x, recursive=recursive)
+
+  x <- listenv(); x$a <- list(B=1:3); x$b <- list(C=1:3, D=4:5)
+  y2 <- unlist(x, recursive=recursive)
+  stopifnot(identical(y2, y1))
+} # for (recursive ...)
+
+x <- listenv(); x$a <- list(B=1:3); x$b <- as.listenv(list(C=1:3, D=4:5))
+y3 <- unlist(x, recursive=TRUE)
+stopifnot(identical(y3, y1))
+
+x <- listenv()
+y <- unlist(x)
+stopifnot(length(y) == 0)
+stopifnot(is.null(y))
+
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Comparisons
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv(c=NULL, .a=3, b=1)
+print(x)
+
+## A list environment is always equal to itself
+stopifnot(all.equal(x, x))
+
+## List environments emulate lists
+stopifnot(all.equal(x, list(c=NULL, .a=3, b=1)))
+stopifnot(all.equal(x, list(c=NULL, .a=3, b=1), sorted=TRUE))
+stopifnot(all.equal(x, list(.a=3, b=1, c=NULL), sorted=TRUE))
+
+stopifnot(all.equal(x, list(c=NULL, b=1), all.names=FALSE))
+stopifnot(all.equal(x, list(.a=3, c=NULL, b=1), all.names=FALSE))
+stopifnot(all.equal(x, list(b=1, c=NULL), all.names=FALSE, sorted=TRUE))
+
+res <- all.equal(x, list(b=1, c=NULL), sorted=FALSE)
+stopifnot(!isTRUE(res))
+
+res <- all.equal(x, list(b=1, c=NULL), all.names=FALSE)
+stopifnot(!isTRUE(res))
+
+## Assert listenv() -> as.list() -> as.listenv() equality
+y <- as.list(x)
+stopifnot(identical(names(y), names(x)))
+z <- as.listenv(y)
+stopifnot(identical(names(z), names(y)))
+stopifnot(all.equal(x, y))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Warnings
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+x[1:3] <- 1:3
+res <- tryCatch(x[1:2] <- 1:4, warning=function(w) {
+  class(w) <- "try-warning"
+  w
+})
+stopifnot(inherits(res, "try-warning"))
+
+res <- tryCatch(x[1:3] <- 1:2, warning=function(w) {
+  class(w) <- "try-warning"
+  w
+})
+stopifnot(inherits(res, "try-warning"))
+
+res <- tryCatch(x[integer(0L)] <- 1, warning=function(w) {
+  class(w) <- "try-warning"
+  w
+})
+stopifnot(!inherits(res, "try-warning"))
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Exception handling
+## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+x <- listenv()
+length(x) <- 3L
+names(x) <- c("a", "b", "c")
+
+res <- try(names(x) <- c("a", "b"), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[1:2]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[0]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[length(x)+1]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[1+2i]], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[1+2i], silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[1+2i]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[1+2i] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[integer(0L)]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[1:2]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[Inf]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[0]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[-1]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[character(0L)]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[c("a", "b")]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(x[[""]] <- 1, silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+
+## Cleanup
+options(oopts)
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/parse_env_subset,dimensions.R b/tests/parse_env_subset,dimensions.R
new file mode 100644
index 0000000..e2783e6
--- /dev/null
+++ b/tests/parse_env_subset,dimensions.R
@@ -0,0 +1,105 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+if (exists("x")) rm(list="x")
+if (exists("y")) rm(list="y")
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Multi-dimensional subsetting
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("*** parse_env_subset() on multi-dimensional listenv ...")
+
+x <- listenv()
+length(x) <- 6
+dim(x) <- c(2,3)
+
+target <- parse_env_subset(x[[2]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 2, !target$exists)
+
+target <- parse_env_subset(x[[1,2]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 3, !target$exists)
+
+x[[1,2]] <- 1.2
+target <- parse_env_subset(x[[1,2]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 3, target$exists)
+
+target <- parse_env_subset(x[[1,4]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), is.na(target$idx), !target$exists)
+
+## Assert that x[[1,4]] is not the same as x[[c(1,4)]]
+target <- parse_env_subset(x[[1,4]], substitute=TRUE)
+str(target)
+target2 <- parse_env_subset(x[[c(1,4)]], substitute=TRUE)
+str(target2)
+target$code <- target2$code <- NULL
+stopifnot(!isTRUE(all.equal(target2, target)))
+
+
+dimnames(x) <- list(c("a", "b"), c("A", "B", "C"))
+print(x)
+
+target <- parse_env_subset(x[["a",2]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 3, target$exists)
+
+target <- parse_env_subset(x[["a","B"]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 3, target$exists)
+
+target <- parse_env_subset(x["a","B"], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$idx == 3, target$exists)
+
+target <- parse_env_subset(x["a",1:3], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), length(target$idx) == 3, all(target$idx == c(1,3,5)))
+
+target <- parse_env_subset(x["a",], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), length(target$idx) == 3, all(target$idx == c(1,3,5)))
+
+target <- parse_env_subset(x["a",-1], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), length(target$idx) == 2, all(target$idx == c(3,5)))
+
+message("*** parse_env_subset() on multi-dimensional listenv ... DONE")
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Exception handling
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("*** parse_env_subset() on multi-dimensional listenv - exceptions ...")
+
+x <- listenv()
+
+## Multidimensional subsetting on 'x' without dimensions
+res <- try(target <- parse_env_subset(x[[1,2]], substitute=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+## Multi-dimensional subsetting
+x <- listenv()
+length(x) <- 6
+dim(x) <- c(2,3)
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - - -
+## FIXME: Should zero indices give parse errors or not?
+## - - - - - - - - - - - - - - - - - - - - - - - - - - -
+res <- try(target <- parse_env_subset(x[[0]], substitute=TRUE), silent=TRUE)
+## stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(x[[1,0]], substitute=TRUE), silent=TRUE)
+## stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(x[[1,2,3]], substitute=TRUE), silent=TRUE)
+## stopifnot(inherits(res, "try-error"))
+
+message("*** parse_env_subset() on multi-dimensional listenv - exceptions ... DONE")
+
+
+## Cleanup
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/parse_env_subset.R b/tests/parse_env_subset.R
new file mode 100644
index 0000000..b151baa
--- /dev/null
+++ b/tests/parse_env_subset.R
@@ -0,0 +1,222 @@
+library("listenv")
+
+ovars <- ls(envir=globalenv())
+if (exists("x")) rm(list="x")
+if (exists("y")) rm(list="y")
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Variable in global/parent environment
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("*** parse_env_subset() on parent environment ...")
+
+target <- parse_env_subset(x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+          target$name == "x", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset("x", substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+          target$name == "x", is.na(target$idx), !target$exists)
+
+x <- NULL
+target <- parse_env_subset(x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+          target$name == "x", is.na(target$idx), target$exists)
+
+target <- parse_env_subset(y, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+          target$name == "y", is.na(target$idx), !target$exists)
+
+message("*** parse_env_subset() on parent environment ... DONE")
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Environment
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("parse_env_subset() on environment ...")
+x <- new.env()
+
+target <- parse_env_subset(x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+          target$name == "x", is.na(target$idx), target$exists)
+
+target <- parse_env_subset(x$a, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset("a", envir=x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset(x[["a"]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset("a", envir=x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+res <- try(target <- parse_env_subset(1, substitute=FALSE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(x[[1]], substitute=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+x$a <- 1
+target <- parse_env_subset(x$a, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), target$exists)
+
+message("parse_env_subset() on environment ... DONE")
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## List environment
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("*** parse_env_subset() on listenv ...")
+
+x <- listenv()
+
+target <- parse_env_subset(x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, environment()),
+          target$name == "x", is.na(target$idx), target$exists)
+
+target <- parse_env_subset(x$a, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset(x[["a"]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset("a", envir=x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", is.na(target$idx), !target$exists)
+
+target <- parse_env_subset(x[[1]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "", target$idx == 1, !target$exists)
+
+target <- parse_env_subset(x[[2]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "", target$idx == 2, !target$exists)
+
+x$a <- 1
+target <- parse_env_subset(x$a, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", target$idx  == 1, target$exists)
+
+target <- parse_env_subset("a", envir=x, substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", target$idx  == 1, target$exists)
+
+stopifnot(x$a == 1)
+stopifnot(x[[1]] == 1)
+
+target <- parse_env_subset(x[[1]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", target$idx  == 1, target$exists)
+
+
+x[[3]] <- 3
+target <- parse_env_subset(x[[3]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "", target$idx  == 3, target$exists)
+stopifnot(x[[3]] == 3)
+print(names(x))
+stopifnot(identical(names(x), c("a", "", "")))
+
+
+b <- 1
+target <- parse_env_subset(x[[b]], substitute=TRUE)
+str(target)
+stopifnot(identical(target$envir, x), target$name == "a", target$idx  == 1, target$exists)
+
+
+x <- listenv()
+length(x) <- 2
+
+target <- parse_env_subset(x[[1]], substitute=TRUE)
+str(target)
+stopifnot(!target$exists)
+
+target <- parse_env_subset(x[[2]], substitute=TRUE)
+str(target)
+stopifnot(!target$exists)
+
+target <- parse_env_subset(x[[3]], substitute=TRUE)
+str(target)
+stopifnot(!target$exists)
+stopifnot(length(x) == 2)
+
+x[[2]] <- 2
+target <- parse_env_subset(x[[2]], substitute=TRUE)
+str(target)
+stopifnot(target$exists)
+
+x[[4]] <- 4
+stopifnot(length(x) == 4)
+
+target <- parse_env_subset(x[[3]], substitute=TRUE)
+str(target)
+stopifnot(!target$exists)
+
+target <- parse_env_subset(x[1:5], substitute=TRUE)
+stopifnot(length(target$idx) == 5, all(target$idx == 1:5))
+str(target)
+
+target <- parse_env_subset(x[integer(0L)], substitute=TRUE)
+stopifnot(length(target$idx) == 0)
+str(target)
+
+target <- parse_env_subset(x[[integer(0L)]], substitute=TRUE)
+stopifnot(length(target$idx) == 0)
+str(target)
+
+target <- parse_env_subset(x[0], substitute=TRUE)
+stopifnot(length(target$idx) == 0)
+str(target)
+
+target <- parse_env_subset(x[-1], substitute=TRUE)
+stopifnot(length(target$idx) == length(x)-1)
+str(target)
+
+## Odds and ends
+target <- parse_env_subset(x[[""]], substitute=TRUE)
+stopifnot(length(target$idx) == 1L, !target$exists)
+
+message("*** parse_env_subset() on listenv ... DONE")
+
+
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+## Exception handling
+## - - - - - - - - - - - - - - - - - - - - - - - - - -
+message("*** parse_env_subset() - exceptions ...")
+
+x <- listenv()
+
+res <- try(target <- parse_env_subset(x[[0]], substitute=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset("_a", substitute=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(1:10, envir=x, substitute=FALSE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(c("a", "b"), envir=x, substitute=FALSE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+res <- try(target <- parse_env_subset(x at a, substitute=TRUE), silent=TRUE)
+stopifnot(inherits(res, "try-error"))
+
+message("*** parse_env_subset() - exceptions ... DONE")
+
+
+## Cleanup
+rm(list=setdiff(ls(envir=globalenv()), ovars), envir=globalenv())
diff --git a/tests/undim.R b/tests/undim.R
new file mode 100644
index 0000000..1ba6ac0
--- /dev/null
+++ b/tests/undim.R
@@ -0,0 +1,33 @@
+library("listenv")
+
+message("*** undim() ...")
+
+## General
+x <- c(a=1, b=2, A=3, B=4)
+names <- names(x)
+dim <- c(2,2)
+dimnames <- list(c("a", "b"), c("A", "B"))
+
+## Basic arrays
+y <- array(x, dim=dim, dimnames=dimnames)
+names(y) <- names
+z <- undim(y)
+stopifnot(identical(names(z), names))
+
+## Lists
+y <- as.list(x)
+dim(y) <- dim
+dimnames(y) <- dimnames
+names(y) <- names
+z <- undim(y)
+stopifnot(identical(names(z), names))
+
+## List environments
+y <- as.listenv(x)
+dim(y) <- dim
+dimnames(y) <- dimnames
+names(y) <- names
+z <- undim(y)
+stopifnot(identical(names(z), names))
+
+message("*** undim() ... DONE")
diff --git a/tests/utils.R b/tests/utils.R
new file mode 100644
index 0000000..cdd50d4
--- /dev/null
+++ b/tests/utils.R
@@ -0,0 +1,43 @@
+printf <- function(...) cat(sprintf(...))
+hpaste <- listenv:::hpaste
+
+# Some vectors
+x <- 1:6
+y <- 10:1
+z <- LETTERS[x]
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Abbreviation of output vector
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+printf("x = %s.\n", hpaste(x))
+## x = 1, 2, 3, ..., 6.
+
+printf("x = %s.\n", hpaste(x, maxHead=2))
+## x = 1, 2, ..., 6.
+
+printf("x = %s.\n", hpaste(x), maxHead=3) # Default
+## x = 1, 2, 3, ..., 6.
+
+# It will never output 1, 2, 3, 4, ..., 6
+printf("x = %s.\n", hpaste(x, maxHead=4))
+## x = 1, 2, 3, 4, 5 and 6.
+
+# Showing the tail
+printf("x = %s.\n", hpaste(x, maxHead=1, maxTail=2))
+## x = 1, ..., 5, 6.
+
+# Turning off abbreviation
+printf("y = %s.\n", hpaste(y, maxHead=Inf))
+## y = 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
+
+## ...or simply
+printf("y = %s.\n", paste(y, collapse=", "))
+## y = 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Adding a special separator before the last element
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Change last separator
+printf("x = %s.\n", hpaste(x, lastCollapse=" and "))
+## x = 1, 2, 3, 4, 5 and 6.
diff --git a/vignettes/listenv.md.rsp b/vignettes/listenv.md.rsp
new file mode 100644
index 0000000..c5e1de6
--- /dev/null
+++ b/vignettes/listenv.md.rsp
@@ -0,0 +1,360 @@
+<%@meta language="R-vignette" content="--------------------------------
+%\VignetteIndexEntry{List Environments}
+%\VignetteAuthor{Henrik Bengtsson}
+%\VignetteKeyword{R}
+%\VignetteKeyword{package}
+%\VignetteKeyword{vignette}
+%\VignetteKeyword{listenv}
+%\VignetteEngine{R.rsp::rsp}
+%\VignetteTangle{FALSE}
+--------------------------------------------------------------------"%>
+<%
+R.utils::use("R.utils")
+use("listenv")
+options("withCapture/newline"=FALSE)
+%>
+# <%@meta name="title"%>
+
+_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  |              |
+| Indexed elements, e.g. `x[[4]]`                                        |              yes  |   yes  |              |
+| Dimensions, e.g. `dim(x)`                                              |              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]`, `[2:1,,3]`   |              yes  |   yes  |              |
+| Multi-element subsetting preserves element names                       |              yes  |        |              |
+| Removing element by assigning NULL, e.g. `x$c <- NULL`                 |              yes  |   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,
+```r
+<%=withCapture({
+x <- listenv(a=1, b=2, c="hello")
+x
+
+length(x)
+names(x)
+x$a
+x[[3]] <- toupper(x[[3]])
+x$c
+
+y <- x
+y$d <- y$a + y[["b"]]
+names(y)[2] <- "a"
+y$a
+y
+identical(y, x)
+
+for (ii in seq_along(x)) {
+  cat(sprintf("Element %d (%s): %s\n", ii, sQuote(names(x)[ii]), x[[ii]]))
+}
+<%---
+get(map(x)["b"], envir=x)
+assign(map(x)["b"], 3, envir=x)
+---%>
+
+x[c(1,3)] <- list(2, "Hello world!")
+
+x
+y <- as.list(x)
+str(y)
+z <- as.listenv(y)
+z
+identical(z, x)
+all.equal(z, x)
+})%>
+```
+
+## Creating list environments
+List environments are created similarly to lists but also similarly to environments.  To create an empty list environment, use
+```r
+<%=withCapture({
+x <- listenv()
+x
+})%>
+```
+This can later can be populated using named assignments,
+```r
+<%=withCapture({
+x$a <- 1
+x
+})%>
+```
+comparable to how both lists and environments work.  Similarly to lists, they can also be populated using indices, e.g.
+```r
+<%=withCapture({
+x[[2]] <- 2
+x$c <- 3
+x
+})%>
+```
+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
+<%=withCapture({
+x[[5]] <- 5
+x
+x[[4]]
+})%>
+```
+
+As with lists, the above list environment can also be created from the start, e.g.
+```r
+<%=withCapture({
+x <- listenv(a=1, 3, c=4, NULL, 5)
+x
+})%>
+```
+
+
+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
+<%=withCapture({
+x
+length(x) <- 2
+x
+x[[1]]
+x[[2]]
+})%>
+```
+If increased, new elements are populated with unnamed elements of `NULL`, e.g.
+```r
+<%=withCapture({
+length(x) <- 4
+x
+x[[3]]
+x[[4]]
+})%>
+```
+
+To allocate an "empty" list environment (with all `NULL`:s) of a given length, do
+```r
+<%=withCapture({
+x <- listenv()
+length(x) <- 4
+x
+})%>
+```
+_Note_: Unfortunately, it is _not_ possible to use `x <- vector("listenv", length=4)`; that construct is only supported for the basic data types.
+
+
+
+## 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
+<%=withCapture({
+x <- listenv(a=1, b=2, c=3)
+for (name in names(x)) {
+  cat(sprintf("Element %s: %s\n", sQuote(name), x[[name]]))
+}
+})%>
+```
+
+### 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
+<%=withCapture({
+x <- listenv(a=1, b=2, c=3)
+for (ii in seq_along(x)) {
+  cat(sprintf("Element %d: %s\n", ii, x[[ii]]))
+}
+})%>
+```
+
+
+## Coercion to and from list environments
+
+### Coercing to lists and vectors
+
+Coercing a list environment to a list:
+```r
+<%=withCapture({
+x <- listenv(a=2, b=3, c="hello")
+x
+y <- as.list(x)
+str(y)
+})%>
+```
+
+Coercing a list to a list environment:
+```r
+<%=withCapture({
+z <- as.listenv(y)
+z
+identical(z, x)
+all.equal(z, x)
+})%>
+```
+
+Unlisting:
+```r
+<%=withCapture({
+unlist(x)
+unlist(x[-3])
+unlist(x[1:2], use.names=FALSE)
+})%>
+```
+
+
+## Multi-dimensional list environments
+
+Analogously to lists, and contrary to plain environments, list environments can have dimensions with corresponding names.  For example,
+```r
+<%=withCapture({
+x <- as.listenv(1:6)
+dim(x) <- c(2,3)
+dimnames(x) <- list(c("a", "b"), c("A", "B","C"))
+x
+})%>
+```
+An easy way to quickly get an overview is to coerce to a list, e.g.
+```r
+<%=withCapture({
+as.list(x)
+})%>
+```
+Individual elements of a list environment can be accessed using standard subsetting syntax, e.g.
+```r
+<%=withCapture({
+x[["a", "B"]]
+x[[1, 2]]
+x[[1, "B"]]
+})%>
+```
+We can assign individual elements similarly, e.g.
+```r
+<%=withCapture({
+x[["b", "B"]] <- -x[["b", "B"]]
+as.list(x)
+})%>
+```
+We can also assign multiple elements through dimensional subsetting, e.g.
+```r
+<%=withCapture({
+x[2,-1] <- 98:99
+as.list(x)
+x["a",c(1,3)] <- list(97, "foo")
+as.list(x)
+x[] <- 1:6
+as.list(x)
+})%>
+```
+
+
+Concurrently with dimensional names it is possible to have names of the invidual elements just as for list environments without dimensions.  For example,
+```r
+<%=withCapture({
+names(x) <- letters[seq_along(x)]
+x
+x[["a"]]
+x[["f"]]
+x[c("a", "f")]
+unlist(x)
+})%>
+```
+Contrary to lists, element names are preserved also with multi-dimensional subsetting, e.g.
+```r
+<%=withCapture({
+x[1,2]
+x[1,2,drop=FALSE]
+x[1:2,2:1]
+x[2,]
+x[2,,drop=FALSE]
+x["b",-2,drop=FALSE]
+})%>
+```
+
+
+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")`.
+<%---
+Because of this, the listenv package provides the `undim()` function, which removes the dimensions but preserves the names, e.g.
+```r
+<%=withCapture({
+x <- undim(x)
+names(x)
+})%>
+```
+_Warning_: Since list environments _and their attributes_ are mutable, calling
+```r
+undim(x)
+```
+will have the same effect as
+```r
+x <- undim(x)
+```
+That is, the dimension attributes of `x` will be changed.  The reason for this is explained in Section 'Important about environments' above.
+---%>
+
+### Limitations
+The current implementation does _not_ support _dimensional subsetting_ of more than one element.  For instance, `x[1,]` is not supported by this version.
+
+
+
+## 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
+<%=withCapture({
+setA <- function(x) {
+  x$a <- 0
+  x
+}
+})%>
+```
+If we pass a regular list to this function,
+```r
+<%=withCapture({
+x <- list(a=1)
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+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
+<%=withCapture({
+x <- new.env()
+x$a <- 1
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+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.
+```r
+<%=withCapture({
+x <- listenv(a=1)
+y <- setA(x)
+x$a
+y$a
+})%>
+```
+
+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
+<%=withCapture({
+x <- listenv(a=1)
+y <- x
+attr(y, "foo") <- "Hello!"
+attr(x, "foo")
+})%>
+```
+
+
+[listenv]: http://cran.r-project.org/package=listenv
+
+---
+Copyright Henrik Bengtsson, 2015

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-listenv.git



More information about the debian-med-commit mailing list