An open API service indexing awesome lists of open source software.

https://github.com/moodymudskipper/elephant

make variables remember their history
https://github.com/moodymudskipper/elephant

Last synced: 2 days ago
JSON representation

make variables remember their history

Awesome Lists containing this project

README

        

---
output: github_document
---

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
```

# elephant

> Elephants never forget!

*elephant* is a quick experiment on how to make variables remember their
history.

Use `:=` to record a call.

## Installation

Install with:

``` r
remotes::install_github("moodymudskipper/elephant")
```

## Example

```{r example}
library(elephant)
```

We build an elephant object by using `:=`

```{r}
x := 1
y := 3
z := x + y
x := x * 2
bar <- x + y + z
foo := x + sqrt(z) + bar

x
y
z
bar
foo
```

Here we see `bar` is not an elephant object because it doesn't use `:=` and
`+.elephant` doesn't preserve class and attributes.

However `baz <- x` would just copy the object, so in this case it's advised to do
either `baz := x` or `baz <- forget(x)`

```{r}
# not good
baz <- x
baz

# good
baz := x
baz

# good
baz <- forget(x)
baz
```

We can access the variables used to compute our elephant object by using
`list_calves()` or `calf()`.

```{r}
list_calves(foo)
list_calves(foo, "z")
calf(foo, "z")
calf(foo, "z", "x")
```

## Debugging with *elephant*

If an elephant calls fails, its memory is preserved and can be recovered using
`poach()`, see the example below :

```{r, error = TRUE}

test <- function(){
x := 1
y := 3
z := x * 2
bar <- x + y + z
foo := x + sqrt(z) + bar * letters # <- problematic call!
}

test()

poach()

# extract the bar variable as used by foo, for further investigation
foo_poached <- poach()
calf(foo_poached, "letters")

# we can go deeper
calf(foo_poached, "z", "x")
```

## Benchmark

`:=` has a small overhead, it shouldn't slow your code much most of the time:

```{r}
bench::mark(
elephant_assignment = (foo := x + sqrt(z) + bar),
standard_assignment = (foo <- x + sqrt(z) + bar),
check = FALSE)
```

However it wouldn't not be well advised, nor really that useful, to use it in
a loop growing an object as below.

```{r}
x := 1
for (i in 1:4) {
x:= x + 1
}
x
```

It's especially true for bigger objects as we'd be keeping in memory all previous values
that would have been flushed by the garbage collector if we hadn't used *elephant*.