’Round and ’round the R workspace, code-monkey chased the {weasel}
Code-monkey stopped to extract the first val…
pop()
goes the {weasel}
You can install the development version of weasel like so:
# install.packages("remotes")
remotes::install_github("jonocarroll/weasel")
Inspired by other (less functional/immutable) languages where one might
be familiar with being able to pop()
a value from the top of the
stack, this package provides similar functionality.
If we define a vector a
containing some values
a <- c(3, 1, 4, 1, 5, 9)
and we wish to extract the first value, we can certainly do so with
a[1]
#> [1] 3
but, due to the nature of R, the vector a
is unchanged
a
#> [1] 3 1 4 1 5 9
Instead, we could remove the first value of a
with
a[-1]
#> [1] 1 4 1 5 9
but again, a
remains unchanged - in order to modify a
we must
redefine it as e.g.
a <- a[-1]
a
#> [1] 1 4 1 5 9
This differs from other languages were we might want to extract that first value for use, and leave the rest of the vector in place.
{weasel} offers that functionality
a <- c(3, 1, 4, 1, 5, 9)
a
#> [1] 3 1 4 1 5 9
first_val <- pop(a)
a
#> [1] 1 4 1 5 9
first_val
#> [1] 3
Note that this did not require explicitly1 redefining a
; the return
value of pop()
is the popped value.
This is also a Generic, so it is defined for some classes (vector
,
list
, data.frame
) and can be extended.
With a list
:
a <- list(x = c(2, 3), y = c("foo", "bar"), z = c(3.1, 4.2, 6.9))
a
#> $x
#> [1] 2 3
#>
#> $y
#> [1] "foo" "bar"
#>
#> $z
#> [1] 3.1 4.2 6.9
x <- pop(a)
a
#> $y
#> [1] "foo" "bar"
#>
#> $z
#> [1] 3.1 4.2 6.9
x
#> [1] 2 3
With a data.frame
:
a <- data.frame(x = c(2, 3, 4), y = c("foo", "bar", "baz"), z = c(3.1, 4.2, 6.9))
a
#> x y z
#> 1 2 foo 3.1
#> 2 3 bar 4.2
#> 3 4 baz 6.9
x <- pop(a)
a
#> x y z
#> 2 3 bar 4.2
#> 3 4 baz 6.9
x
#> x y z
#> 1 2 foo 3.1
{weasel} also offers push()
functionality
a <- c(1, 4, 1, 5, 9)
a
#> [1] 1 4 1 5 9
push(a, 3)
a
#> [1] 3 1 4 1 5 9
which is also Generic
a <- list(y = c("foo", "bar"), z = c(3.1, 4.2, 6.9))
a
#> $y
#> [1] "foo" "bar"
#>
#> $z
#> [1] 3.1 4.2 6.9
push(a, list(new_vals = c(99, 77)))
a
#> $new_vals
#> [1] 99 77
#>
#> $y
#> [1] "foo" "bar"
#>
#> $z
#> [1] 3.1 4.2 6.9
a <- data.frame(y = c("foo", "bar", "baz"), z = c(3.1, 4.2, 6.9))
a
#> y z
#> 1 foo 3.1
#> 2 bar 4.2
#> 3 baz 6.9
push(a, data.frame(y = 99, z = 77))
a
#> y z
#> 1 99 77.0
#> 2 foo 3.1
#> 3 bar 4.2
#> 4 baz 6.9
Please note that this is intended only for informal use and education. This functionality is not idiomatic R and is more of a footgun than should be tolerated.
Internally, the evaluation of each ‘method’ is a macro defined using
gtools::defmacro()
which enables substitution of input arguments and
redefinition in the parent namespace.
This
blogpost
covers the full details. This
article in the R
News Newsletter covers the construction and motivation of defmacro()
.
Footnotes
-
It certainly is redefined internally, but the use of
defmacro()
means it does not need to be explicit ↩