An Introduction to ‘refer’ References

Christopher Mann

2021-11-06

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)

Creating References, Reference Expressions, and Slices

Our goal with this project is to population a map. We will use a character matrix and keep it small at 10x10.

map   <- matrix(' ', nrow=10, ncol=10)
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.

person <- list(
  map     = ref(map), 
  row     = 1,
  col     = 1
)
map[1,1] <- 'X'

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.

person$map
#> <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.

map[1,5] <- "O"
!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.

location <- ref(c(person$row, person$col))
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.

person$row <- person$row + 1
!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.

location + 1
#> [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.

row1 <- slice(map, 1, 1:5)
!row1
#> [1] "X" " " " " " " "O"
map[1, 3] <- "%"
!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.

loc_row <- slice(location, 1)
!loc_row
#> [1] 2

Modifying Variables In Place

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.

person$col %+=% 3
person$col
#> [1] 4
person$col %-=% 3
person$col
#> [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!

x <- 1:nrow(map)
slice_x <- slice(x, 3:6)
slice_x %+=% 10
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

Safer References

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.

p <- sref(person)
!p
#> $map
#> <environment: R_GlobalEnv> => map
#> $row
#> [1] 2
#> 
#> $col
#> [1] 1
## These will spawn an error. Don't run!
p$row
modify_by(p, function(x){ x$row <- x$row + 1; x })