The refer
package allows users to keep references to objects and modify objects in place with relying on reference classes. This article describes how to use ref
objects by moving objects around a map. Please note that many of the operations in the refer
package go against the philosophy of R and may lead to inconsistent and unclear code; use sparingly and with caution.
First, we need to load the refer
package.
library(refer)
Our goal with this project is to population a map. We will use a character matrix and keep it small at 10x10.
<- matrix(' ', nrow=10, ncol=10)
map
map#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#> [1,] " " " " " " " " " " " " " " " " " " " "
#> [2,] " " " " " " " " " " " " " " " " " " " "
#> [3,] " " " " " " " " " " " " " " " " " " " "
#> [4,] " " " " " " " " " " " " " " " " " " " "
#> [5,] " " " " " " " " " " " " " " " " " " " "
#> [6,] " " " " " " " " " " " " " " " " " " " "
#> [7,] " " " " " " " " " " " " " " " " " " " "
#> [8,] " " " " " " " " " " " " " " " " " " " "
#> [9,] " " " " " " " " " " " " " " " " " " " "
#> [10,] " " " " " " " " " " " " " " " " " " " "
Next, let us create a person to place on the map. The person will keep track of its location and a reference to the map object. We will place a representative of the person on the map.
<- list(
person map = ref(map),
row = 1,
col = 1
)1,1] <- 'X' map[
Since a reference of the map is placed inside 'person'
, we can always use it to indirectly access the map. Just calling person$map
, though, only returns the reference not the actual item.
$map
person#> <environment: R_GlobalEnv> => map
To return the underlying object, we must ‘dereference’ the item using either the deref()
function or the !
operator.
!person$map
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#> [1,] "X" " " " " " " " " " " " " " " " " " "
#> [2,] " " " " " " " " " " " " " " " " " " " "
#> [3,] " " " " " " " " " " " " " " " " " " " "
#> [4,] " " " " " " " " " " " " " " " " " " " "
#> [5,] " " " " " " " " " " " " " " " " " " " "
#> [6,] " " " " " " " " " " " " " " " " " " " "
#> [7,] " " " " " " " " " " " " " " " " " " " "
#> [8,] " " " " " " " " " " " " " " " " " " " "
#> [9,] " " " " " " " " " " " " " " " " " " " "
#> [10,] " " " " " " " " " " " " " " " " " " " "
If we add a new object to the original map, then the change is reflected when it is dereferenced from person
.
1,5] <- "O"
map[!person$map
#> [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
#> [1,] "X" " " " " " " "O" " " " " " " " " " "
#> [2,] " " " " " " " " " " " " " " " " " " " "
#> [3,] " " " " " " " " " " " " " " " " " " " "
#> [4,] " " " " " " " " " " " " " " " " " " " "
#> [5,] " " " " " " " " " " " " " " " " " " " "
#> [6,] " " " " " " " " " " " " " " " " " " " "
#> [7,] " " " " " " " " " " " " " " " " " " " "
#> [8,] " " " " " " " " " " " " " " " " " " " "
#> [9,] " " " " " " " " " " " " " " " " " " " "
#> [10,] " " " " " " " " " " " " " " " " " " " "
ref
can also be used to build expressions that contain references. For example, location
below contains a reference to the row
and col
of person
.Dereferencing location
evaluates the expression, taking note of where person is located when originally created. The effect is similar to creating an active binding. However, active bindings are heavier and much more difficult to pass around, inspect, and so forth.
<- ref(c(person$row, person$col))
location
location#> <c(person$row, person$col)>
!location
#> [1] 1 1
Since location
is a reference, updating either row
or col
will change the dereferenced value of location.
$row <- person$row + 1
person!location
#> [1] 2 1
Note that ref
objects automatically dereference when applied to many base functions such arithmetic operators. This includes the standard extraction operators: $
, [
, and [[
. However, these do not overwrite the underlying data.
+ 1
location #> [1] 3 2
!location
#> [1] 2 1
A slice
is a special type of reference that refers to part of an object. For example, we could create a slice that points to the second row and last 5 columns of the map. If these values change, the slice reflects these changes.
<- slice(map, 1, 1:5)
row1 !row1
#> [1] "X" " " " " " " "O"
1, 3] <- "%"
map[!row1
#> [1] "X" " " "%" " " "O"
When dereferenced, the above slice
calls map[2, 6:10]
within the environment that map is located. Since ref
objects automatically dereference when extraction calls are made, slice
could even be used on another reference.
<- slice(location, 1)
loc_row !loc_row
#> [1] 2
The ref
package contains another of functions to modify objects in place. For example, it includes variations on the standard +=
and -=
operators found in many languages such as Python.
$col %+=% 3
person$col
person#> [1] 4
$col %-=% 3
person$col
person#> [1] 1
These functions can also accept other reference objects. When a reference object is used, the underlying object is modified. This can be dangerous, so use sparingly!
<- 1:nrow(map)
x <- slice(x, 3:6)
slice_x %+=% 10
slice_x
x#> [1] 1 2 13 14 15 16 7 8 9 10
Objects can also be modified in place with custom functions using modify_by
.
modify_by(x, sqrt)
x#> [1] 1.000000 1.414214 3.605551 3.741657 3.872983 4.000000 2.645751 2.828427
#> [9] 3.000000 3.162278
modify_by
can also be used to completely overwrite the value of an object further up the search path by passing a value rather than a function.
modify_by(x, 5)
x#> [1] 5
The general ref
function automatically dereferences when passed to a wide variety of functions and can modify the underlying objects in place. sref
is an alternative version of ref
that does away with this behavior. sref
objects can still be dereferenced as normal, but attempts to modify or apply functions to the reference will throw an error. Use sslice
to create sref
versions of slices.
<- sref(person)
p !p
#> $map
#> <environment: R_GlobalEnv> => map
#> $row
#> [1] 2
#>
#> $col
#> [1] 1
## These will spawn an error. Don't run!
$row
pmodify_by(p, function(x){ x$row <- x$row + 1; x })