1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
|
<!-- README.md is generated from README.Rmd. Please edit that file -->
# bindr
<!-- badges: start -->
[](https://lifecycle.r-lib.org/articles/stages.html)
[](https://github.com/krlmlr/bindr/actions)
[](https://app.codecov.io/github/krlmlr/bindr?branch=master)
[](https://cran.r-project.org/package=bindr)
<!-- badges: end -->
Active bindings in R are much like properties in other languages: They
look like a variable, but querying or setting the value triggers a
function call. They can be created in R via
[`makeActiveBinding()`](https://www.rdocumentation.org/packages/base/versions/3.3.1/topics/bindenv),
but with this API the function used to compute or change the value of a
binding cannot take additional arguments. The `bindr` package faciliates
the creation of active bindings that are linked to a function that
receives the binding name, and an arbitrary number of additional
arguments.
## Installation
You can install `bindr` from GitHub with:
``` r
# install.packages("devtools")
devtools::install_github("krlmlr/bindr")
```
## Getting started
For illustration, the `append_random()` function is used. This function
appends a separator (a dash by default) and a random letter to its
input, and talks about it, too.
``` r
set.seed(20161510)
append_random <- function(x, sep = "-") {
message("Evaluating append_random(sep = ", deparse(sep), ")")
paste(x, sample(letters, 1), sep = sep)
}
append_random("a")
#> Evaluating append_random(sep = "-")
#> [1] "a-h"
append_random("X", sep = "+")
#> Evaluating append_random(sep = "+")
#> [1] "X+k"
```
In this example, we create an environment that contains bindings for all
lowercase letters, which are evaluated with `append_random()`. As a
result, a dash and a random letter are appended to the name of the
binding:
``` r
library(bindr)
env <- create_env(letters, append_random)
ls(env)
#> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s"
#> [20] "t" "u" "v" "w" "x" "y" "z"
env$a
#> Evaluating append_random(sep = "-")
#> [1] "a-s"
env$a
#> Evaluating append_random(sep = "-")
#> [1] "a-h"
env$a
#> Evaluating append_random(sep = "-")
#> [1] "a-c"
env$c
#> Evaluating append_random(sep = "-")
#> [1] "c-o"
env$Z
#> NULL
```
Bindings can also be added to existing environments:
``` r
populate_env(env, LETTERS, append_random, "+")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
#> Evaluating append_random(sep = "-")
env$a
#> Evaluating append_random(sep = "-")
#> [1] "a-q"
env$Z
#> Evaluating append_random(sep = "+")
#> [1] "Z+c"
```
## Further properties
Both named and unnamed arguments are supported:
``` r
create_env("binding", paste, "value", sep = "-")$binding
#> [1] "binding-value"
```
A parent environment can be specified for creation:
``` r
env2 <- create_env("a", identity, .enclos = env)
env2$a
#> a
env2$b
#> NULL
get("b", env2)
#> Evaluating append_random(sep = "-")
#> [1] "b-t"
```
The bindings by default have access to the calling environment:
``` r
create_local_env <- function(names) {
paste_with_dash <- function(...) paste(..., sep = "-")
binder <- function(name, append) paste_with_dash(name, append)
create_env(names, binder, append = "appending")
}
env3 <- create_local_env("a")
env3$a
#> [1] "a-appending"
```
All bindings are read-only:
``` r
env3$a <- NA
#> Error: Binding is read-only.
env3$a <- NULL
#> Error: Binding is read-only.
```
Existing variables or bindings are not overwritten:
``` r
env4 <- as.environment(list(a = 5))
populate_env(env4, list(quote(b)), identity)
ls(env4)
#> [1] "a" "b"
populate_env(env4, letters, identity)
#> Error in populate_env(env4, letters, identity): Not creating bindings for existing variables: b, a
```
## Active bindings and C++
Active bindings must be R functions. To interface with C++ code, one
must bind against an exported Rcpp function, possibly with `rng = false`
if performance matters. The
[`bindrcpp`](https://github.com/krlmlr/bindrcpp#readme) package uses
`bindr` to provide an easy-to-use C++ interface for parametrized active
bindings, and is the recommended way to interface with C++ code. In the
remainder of this section, an alternative using an exported C++ function
is shown.
The following C++ module exports a function
`change_case(to_upper = FALSE)`, which is bound against in R code later.
``` cpp
#include <Rcpp.h>
#include <algorithm>
#include <string>
using namespace Rcpp;
// [[Rcpp::export(rng = FALSE)]]
SEXP change_case(Symbol name, bool to_upper = false) {
std::string name_string = name.c_str();
std::transform(name_string.begin(), name_string.end(),
name_string.begin(), to_upper ? ::toupper : ::tolower);
return CharacterVector(name_string);
}
```
Binding from R:
``` r
env <- create_env(list(as.name("__ToLower__")), change_case)
populate_env(env, list(as.name("__tOuPPER__")), change_case, TRUE)
ls(env)
#> [1] "__ToLower__" "__tOuPPER__"
env$`__ToLower__`
#> [1] "__tolower__"
get("__tOuPPER__", env)
#> [1] "__TOUPPER__"
```
|