---
title: "Container operations for robust code"
output:
  rmarkdown::html_vignette:
    toc: true
    toc_depth: 4
description: >
    Use this vignette if you want to see how container operations can be used
    to make it safer and easier to work with list-like data structures.
    It describes the different methods to add, extract, replace, and remove
    elements and their nuances.
vignette: >
  %\VignetteIndexEntry{Container operations for robust code}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r knitr-setup, include = FALSE}
require(container)
knitr::opts_chunk$set(
  comment = "#",
    error = FALSE,
     tidy = FALSE,
    cache = FALSE,
 collapse = TRUE
)
old <- options(width = 100L)
```

## Container operations
Since R has always been used as an interactive tool, base R list operations
are generous with respect to errors, which can lead to undetected bugs when
developing code.
In contrast, the container package provides additional functions for all basic
operations that allow for fine control to avoid many pitfalls that can happen
with lists and will usually result in more robust code.

### Add elements
New elements can be added "as usual" by concatenation or name.
```{r}
co <- container()
co[["x"]] <- 1
co <- c(co, 2)
co
```
In addition, the container package provides *add*, which
allows to add new elements by name even if that name exists already.
```{r}
co = add(co, x = 3)  # same as c(co, container(x = 3))
co
```

### Replace elements
Basic replacement again can be done "as usual" by name or position.
```{r}
co[["x"]] <- 0
co[[2]] <- 12
co
```
In contrast to base *lists*, the container will not allow to add elements at
positions longer than the length of the object.
```{r, error = TRUE}
co[[4]] <- 4
```

If the name does not exist, the element is appended as known from base lists.
```{r}
co[["y"]] <- 4
co
```

If you want to make sure that something is replaced, *container* provides the
function *replace_at*, which will only replace elements if at names or
positions that exist. The following statements are all equal and show the
different possibilities on how to use *replace_at*.
```{r}
replace_at(co, x = 10, y = 13)            # name = value pairs
replace_at(co, c("x", "y"),  c(10, 13))   # names followed by values
replace_at(co, c(1, 4),      c(10, 13))   # positions followed by values
replace_at(co, list(1, "y"), c(10, 13))   # mixed names/positions followed by values
```

Now see how invalid indices are signaled.
```{r, error = TRUE}
replace_at(co, z = 10)
replace_at(co, "z", 10)
replace_at(co, 5, 10)
```

If you instead want elements at new names to be added, set `.add = TRUE`.
Invalid positional indices are still signaled.
```{r, error = TRUE}
co = replace_at(co, z = 10, .add = TRUE)
co = replace_at(co, 7, 10, .add = TRUE)
co
```

It is also possible to *replace* elements by value, that is, you specify the
value (not the index) that should be replaced. Let's replace the 12 by "foo" and
then 4 by `1:4`.
```{r}
co = replace(co, 12, "foo")
co
co = replace(co, 4, 1:4)
co
```

In an interactive R session you may want to apply the notation using curly
braces.
```{r}
co[[{1:4}]] <- 1:2
co
```

### Extract elements
First of all, standard extract operators apply as expected.
```{r}
co[[1]]
co[["x"]]
co[3:5]
co[c("x", "y", "z")]
```

Programmatically, the corresponding functions to select one or multiple
elements are named *at2* and *at*.
```{r}
at2(co, 1)
at2(co, "x")
at(co, 3:5)
at(co, c("x", "y", "z"))
```

As before you can specify mixed indices via lists.
```{r}
indices = list("x", 4, "z")
at(co, indices)
```

Again, accessing non-existent names or positions is signaled with an error.
```{r, error = TRUE}
at2(co, 10)
at2(co, "a")
at(co, 3:6)
at(co, c("x", "a"))
```

With base R lists non-existent indices usually yield `NULL`.
```{r}
l = list()
l[["a"]]
l[2:3]
```

If needed, the (less strict) *list* access can be mimicked with *peek_at* and
*peek_at2*.
```{r}
peek_at2(co, "a")
peek_at(co, 10, 11)
peek_at(co, 5:10)
```

As you see, one important difference is multiple access via *peek_at* will not
insert `NULL` values by default.
However, both functions in fact allow to specify the default value that is
returned if the index does not exist.
```{r}
peek_at2(co, "a", default = -1)

peek_at(co, "z", "a", .default = -1)
peek_at(co, 4:8, .default = NA)
```


### Remove elements
To remove elements in lists, they have to be replaced by `NULL`.
```{r}
l = list(a = 1)
l
l[["a"]] <- NULL
l
```

With the container package this is done differently, as replacing by `NULL`
will not delete the element but literally replace it by `NULL`.
```{r}
co[["x"]] <- NULL
co
```

Instead, elements can be deleted by index (*delete_at*) or value (*delete*)
as follows.
```{r}
delete_at(co, 1, "y", "z")
delete(co, NULL, 1:2, 10)   # same but remove by value
```

As before, invalid indices or missing values are signaled.
```{r, error = TRUE}
delete_at(co, "a")
delete_at(co, 10)

delete(co, 1:3)
```


If you need a less strict delete operation, use the *discard* functions, which
delete all valid indices/values and ignore the rest.
```{r}
discard_at(co, 1, "a")
discard_at(co, 1:100)

discard(co, NULL, 1:2, 1:3, 1:4)
```


### Combine containers
The *update* function is used to combine/merge two containers.
```{r}
c1 = container(1, b = 2)
c2 = container(   b = 0, c = 3)

update(c1, c2)
update(c2, c1)
```

With the container package this function is also provided for base R lists.
```{r}
l1 = list(1, b = 2)
l2 = list(   b = 0, c = 3)

update(l1, l2)
update(l2, l1)
```



```{r, include = FALSE}
options(old)
```