-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME.Rmd
181 lines (141 loc) · 6.71 KB
/
README.Rmd
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
---
output: github_document
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
options(max.print = 100)
```
# R6methods
<!-- badges: start -->
[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental)
[![R build status](https://github.com/mattwarkentin/R6methods/workflows/R-CMD-check/badge.svg)](https://github.com/mattwarkentin/R6methods/actions)
<!-- badges: end -->
The goal of `R6methods` is to provide a _lightweight_ package that extends the S3 generic support for `R6` class objects. This package defines several S3 methods for common `R` generics (e.g. `str()`) and operators (e.g. `[` or `[<-`) to make it straightforward to define public methods in your `R6` class and have them _"just work"_.
This package is **very experimental** and liable to change drastically. Use at your own risk! Developing this package was primarily a learning experience for working with `R6` and `S3`, and may not have any practical use.
## Installation
You can install the development version of `R6methods` from [GitHub](https://github.com/mattwarkentin/R6methods) with:
``` r
remotes::install_github("mattwarkentin/R6methods")
```
## Usage
### Adding `R6methods` to your package
This package is primarily designed for use by `R` package developers. If you are developing a package which contains `R6` classes, you can save yourself extra work, such as defining `S3` methods for common `R` generics. `R6methods` is meant to be a lightweight addition for providing increased `S3` generic support.
The easiest way to benefit from this package is by depending on `R6methods` in your package `DESCRIPTION` file.
```
Package: mypackage
Title: My Package Title
Version: 0.0.0.9000
Authors@R:
person(given = "Jane",
family = "Doe",
role = c("aut", "cre"),
email = "jane.doe@email.com")
Description: This package...
Depends:
R6methods
```
You may optionally import specific methods using the `@importFrom` `roxygen2` tag.
### Writing dot-dunder methods
In order to benefit from the `S3` methods provided by `R6methods`, you simply need to annotate your `R6` class with so-called **dot-dunder** methods to get immediate support for many common `R` generics. They are called **dot-dunder** because the methods start with a dot (**.**) and **d**ouble-**under**score. This syntax and approach borrows inspiration from the `python` OOP.
These dot-dunder methods must be public methods, and your class must also inherit the `R6` class (i.e. `R6::R6Class(class = TRUE)`, the default). Here is a toy example that adds support to an `R6` class `Foo` for the `[` operator.
```{r}
library(R6methods)
Foo <- R6::R6Class(
public = list(
x = mtcars,
.__subset__ = function(i, j, ...) {
self$x[i, j, ...]
}
)
)
foo <- Foo$new()
# Subset
foo[1:5, 1:3]
```
### Supported methods
The table below is a comprehensive list of the dot-dunder methods currently supported by `R6methods`. When creating your `R6` class, add any number of the dot-dunder methods (with the same function parameters) and gain support for the corresponding S3 method.
```{r supported-methods, echo=FALSE, includes=FALSE}
library(magrittr)
R6methods <-
tibble::tribble(
~Group, ~`Dot-Dunder Method`, ~`S3 Method`, ~Description,
"Arithmetic" ,".__add__(e2)", "`+`", "Addition",
"Arithmetic" ,".__sub__(e2)", "`-`", "Subtraction",
"Arithmetic" ,".__mul__(e2)", "`*`", "Multiplication",
"Arithmetic" ,".__div__(e2)", "`/`", "Divisiion",
"Arithmetic" ,".__pow__(e2)", "`^`", "Powers/exponents",
"Arithmetic" ,".__mod__(e2)", "`%%`", "Modular division",
"Arithmetic" ,".__intdiv__(e2)", "`%/%`", "Integer division",
"Subsetting" ,".__subset__(i, j, ...)", "`[`", "Subset with `[`",
"Subsetting" ,".__subsetrep__(i, j, ..., value)", "`[<-`", "Subset assignment with `[<-`",
"Subsetting" ,".__subset2__(i, j, ...)", "`[[`", "Subset with `[[`",
"Subsetting" ,".__subset2rep__(i, j, ..., value)", "`[[<-`", "Subset assignment with `[[<-`",
"Relational" ,".__lt__(e2)", "`<`", "Less than",
"Relational" ,".__gt__(e2)", "`>`", "Greater than",
"Relational" ,".__le__(e2)", "`<=`", "Less than or equal to",
"Relational" ,".__ge__(e2)", "`>=`", "Greater than or equal to",
"Relational" ,".__eq__(e2)", "`==`", "Equality",
"Relational" ,".__ne__(e2)", "`!=`", "Non-equality",
"Relational" ,".__ae__(e2)", "all.equal", "All equal",
"Misc", ".__data__(...)", "as.data.frame", "Extracts a data frame",
"Misc", ".__length__()", "length", "Returns a custom length, rather the length of the R6 object",
"Misc", ".__names()__", "names", "Returns names, rather the names of the R6 object",
"Misc", ".__str(...)__", "str", "Returns a custom strcuture, rather the structure of the R6 object"
)
tbl <-
gt::gt(
R6methods,
groupname_col = "Group"
) %>%
gt::cols_width(
vars(`Dot-Dunder Method`, `S3 Method`) ~ gt::px(225),
vars(Description) ~ gt::px(300)
)
gt::gtsave(
data = tbl,
filename = "supported_methods.png",
path = "inst/"
)
```
### Iteration
There is one other special method, `.__getitem__(...)`, which, if defined, will allow you easily turn your `R6` class into an iterator. You must also define the `._length__()` method. You can check if your `R6` instance is iterable by calling `R6methods::is.iterable(myR6class)`.
If your `R6` instance is iterable, you can call `R6methods::iter(myR6instance)` to turn your instance into a `coro` iterator. The returned object is a `generator` (i.e. function factory). Calling this `generator` will produce an iterator that iterates the `length()` of your `R6` instance, producing batches of data according to the `.__getitem__()` method.
```{r iterator}
myClass <- R6::R6Class(
classname = "myClass",
public = list(
data = head(mtcars),
.__length__ = function() {
nrow(self$data)
},
.__getitem__ = function(...) {
self$data[..1, , drop = FALSE]
}
)
)
x <- myClass$new() # Create an instance
gen <- iter(x) # Create the generator
ii <- gen() # Create the iterator
# Collect a single batch
coro::collect(ii, 1)
# Collect two batches
coro::collect(ii, 2)
# Collect remaining batches
coro::collect(ii)
coro::collect(ii) # no batches left
coro::is_exhausted(ii()) # iterator is exhausted
# Create new iterator
ii2 <- gen()
# Loop over batches
coro::loop(for (i in ii2) {
print(i)
})
```
## Code of Conduct
Please note that the R6methods project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/0/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms.