A toy package designed to showcase and serve as a reference/tutorial for S7 development. To be used in conjunction with the S7 package webs
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Wrote some more about classes and properties.

+187 -18
+4 -12
README.md
··· 158 158 ) 159 159 160 160 Shape <- new_class("Shape", abstract = TRUE) 161 - Circle <- new_class( 162 - "Circle", 161 + Circle <- new_class("Circle", 163 162 Shape, 164 163 properties = list( 165 164 radius = positive_numeric, 166 165 area = new_property(class_numeric, getter = function(self) pi * self@radius^2, setter = NULL) 167 - ), 168 - constructor = function(radius) { 169 - new_object(S7_object(), radius = radius) 170 - } 166 + ) 171 167 ) 172 - Rect <- new_class( 173 - "Rect", 168 + Rect <- new_class("Rect", 174 169 Shape, 175 170 properties = list( 176 171 width = positive_numeric, 177 172 height = positive_numeric, 178 173 area = new_property(class_numeric, getter = function(self) self@width * self@height, setter = NULL) 179 - ), 180 - constructor = function(width, height) { 181 - new_object(S7_object(), width = width, height = height) 182 - } 174 + ) 183 175 ) 184 176 Square <- new_class("Square", 185 177 Rect,
+183 -6
vignettes/BigNum.Rmd
··· 20 20 21 21 This vignette is written to get you acquainted with `{BigNum}` and `{S7}`, and assumes no knowledge of `{S7}` but does assume basic general knowledge of objected oriented principles and terminology. We highly recommend that you use this package in conjunction with the [S7 website](https://rconsortium.github.io/S7/), but you should also be able to gain a basic understanding of S7 solely through this vignette. We will primarily be using examples from the package. 22 22 23 - <!-- ## Getting Started --> 23 + ## Classes, Properties, & Objects 24 24 25 - We will start with the example from the readme--`Shape`s. We will be constructing a type hierarchy with Squares, Rectangles, Circles, & Shapes, with the typical relationships (e.g. all Squares are Rectangles, all Rectangles and Circles are Shapes). Let us start with `Shape`s though. 25 + [Website](https://rconsortium.github.io/S7/articles/classes-objects.html) 26 + 27 + We will start with the example from the readme--`Shape`s. We will be constructing a type hierarchy with Squares, Rectangles, Circles, and Shapes, with the typical relationships (e.g. all Squares are Rectangles, all Rectangles and Circles are Shapes). Let us start with `Shape`s though. 26 28 27 29 ```{r} 28 30 library(S7) ··· 120 122 121 123 `new_class()` has a `parent` argument which defaults to `S7_object` (think `Object` in Java), which is the parent class of all S7 objects. We can see this using `class()`[^2] 122 124 125 + [^2]: S7 is [built atop of S3](https://rconsortium.github.io/S7/articles/compatibility.html), so methods for S3 OOP will still work on S7 (e.g. `{sloop}` and `class()`). `S7` sometimes exposes some functions which you should use instead, though. Check if there is a `S7` function before defaulting to a S3 one. (e.g. use `S7_class()` instead of checking if `"S7_object"` is in `class(your_object)`). 126 + 123 127 ```{r} 124 128 class(Shape()) 129 + 125 130 class(Circle()) 126 131 ``` 127 132 128 133 Inheritance works as you might expect: Circles now have all the methods of Shapes, as well as `@radius`. 129 134 130 - Think about if the following would return an error, and why. (Hint: check the class definition again.) 135 + Think about if the following would return an error, and why. (Hint: check the class definition for `Shape` again.) 131 136 132 137 ```{r} 133 138 #| eval: false 139 + Shape <- new_class("Shape", 140 + properties = list( 141 + name = class_character, 142 + sides = class_integer 143 + ) 144 + ) 145 + 134 146 Circle("circle", 1, 5) 135 147 ``` 136 148 ··· 173 185 ) 174 186 ``` 175 187 176 - `validator()` is 188 + `validator()` is how a property's values get validated. The first argument we are passing to `new_property()` is `class_numeric`, telling us that data defined by this property can only be of the class `numeric`. Additionally, our validator checks values, and returns a string if there is an issue. Read more about how validation works [here](https://rconsortium.github.io/S7/articles/classes-objects.html#validation-1) but for our purposes, we can just think of a validator as accepting or rejecting values. `positive_numeric` is sensibly defined to only accept positive numerics, and provides an appropriate error message if faced with negative numbers. Let use it. 189 + 190 + ```{r} 191 + Shape <- new_class("Shape", 192 + properties = list( 193 + name = class_character, 194 + sides = new_property(class_integer, 195 + validator = function(value) { 196 + if (value <= 0) "must be greater than 0" 197 + } 198 + ) 199 + ) 200 + ) 201 + 202 + Circle <- new_class("Circle", 203 + Shape, 204 + properties = list( 205 + radius = positive_numeric 206 + ) 207 + ) 208 + ``` 209 + 210 + 211 + Before we see how our new classes have appropriate and intuitive behavior, note that we inlined a definition of a custom property analogous to `positive_numeric` but restricted to `integer`s, for `@sides`. We suggest only doing this when you don't intend to reuse the property--it adds some clutter but defining `positive_integer` and never using it seems more wasteful. 212 + 213 + ```{r} 214 + #| error: true 215 + 216 + Shape("myshape", 0L) 217 + 218 + Circle("circle", -1L, -10) 219 + 220 + Circle("circle", 1L, -10) 221 + ``` 222 + 223 + Note that the property name is automatically added to the validator's error message. Let us further explore properties. 224 + 225 + ```{r} 226 + positive_numeric <- new_property(class_numeric, 227 + validator = function(value) { 228 + if (value <= 0) "must be greater than 0" 229 + } 230 + ) 231 + 232 + Shape <- new_class("Shape", abstract = TRUE) 233 + 234 + Circle <- new_class("Circle", 235 + Shape, 236 + properties = list( 237 + radius = positive_numeric 238 + ) 239 + ) 240 + ``` 241 + 242 + Here we've included what we've written so far, with a small change to `Shape` to disallow instantiation of arbitrary `Shape`s. 243 + 244 + ```{r} 245 + #| error: true 246 + Shape() 247 + ``` 248 + 249 + Let's look at some more interesting things we can do with properties. 250 + 251 + ```{r} 252 + Circle <- new_class("Circle", 253 + Shape, 254 + properties = list( 255 + radius = positive_numeric, 256 + area = new_property(class_numeric, getter = function(self) pi * self@radius^2, setter = NULL) 257 + ) 258 + ) 259 + ``` 260 + 261 + Here, we are defining the area of a `Circle` through a property, but we aren't setting that property explicitly at construction, like we are with `@radius`. Instead, we are creating a [computed property](https://rconsortium.github.io/S7/articles/classes-objects.html#computed-properties), which also happens to be [frozen](https://rconsortium.github.io/S7/articles/classes-objects.html#frozen-properties). What that means is that `@area` is calculated when you call it, and the value itself cannot be set. The `getter` is a function in a property that defines what happens when you call `object@property`. It must always take only one argument, self, and returns the value of the property. Here, we can see that `@area`'s getter is defined to grab `@radius` and use that to calculate the area of a circle. The setter defines what happens when you do `obj@property <- value`. In this case, you technically don't need to set setter to `NULL` as that is its default value and would make `@area` a read only property. We advocate for explicitly setting the setter to `NULL` to make it very clear that `@area` is a read only property. 262 + 263 + ```{r} 264 + #| error: true 265 + circ <- Circle(10) 266 + 267 + circ@area 268 + circ@radius <- 20 269 + circ@area 270 + 271 + circ@area <- 0 272 + ``` 273 + 274 + Circles are getting boring; let's add Rectangles and Squares! 275 + 276 + ```{r} 277 + Rect <- new_class("Rect", 278 + Shape, 279 + properties = list( 280 + width = positive_numeric, 281 + height = positive_numeric, 282 + area = new_property(class_numeric, getter = function(self) self@width * self@height, setter = NULL) 283 + ) 284 + ) 285 + ``` 286 + 287 + I hope by now the class definition should be pedestrian. The only real difference between `Rect` and `Circle` is that `Rect` has an extra property and a different means to compute area. 288 + 289 + ```{r} 290 + Rect(10, 6) 291 + Rect(10, 10) 292 + ``` 293 + 294 + We can easily extend our shapes to further include `Square`s. 295 + 296 + ```{r} 297 + Square <- new_class("Square", 298 + Rect, 299 + constructor = function(side) { 300 + force(side) 301 + new_object(S7_object(), width = side, height = side) 302 + } 303 + ) 304 + ``` 177 305 178 - <!-- https://rconsortium.github.io/S7/articles/classes-objects.html#validation-1 --> 306 + This is strange though--this class definition is far shorter than `Circle` or `Rect`--what's going on? Here, we are leveraging the idea that all Squares are Rectangles to offload most of the heavy work to `Rect`. We are essentially providing `Square` as an edge case `Rect` with well defined behavior. All we need is to define the parent of `Square` as `Rect` (whose parent is `Shape` whose parent is `S7_object`), and define a short custom constructor. As you can see, the constructor is almost a regular R function--it just ["must always end with a call to `new_object()`"](https://rconsortium.github.io/S7/articles/classes-objects.html#constructors). We can set default values of the object in the constructor itself, or could pass a default value in the property definition. 179 307 180 - [^2]: S7 is [built atop of S3](https://rconsortium.github.io/S7/articles/compatibility.html), so S3 methods still work like `{sloop}` and `class()`. `S7` sometimes exposes some functions which you should use instead, though. Check if there is a `S7` function before defaulting to a S3 one. (e.g. use `S7_class()` instead of checking if `S7_object` is in `class(your_object)`). 308 + ```{r} 309 + Square@constructor 310 + Rect@constructor 311 + ``` 312 + 313 + We didn't set a default value for `@side` in the constructor, meaning that this won't work: 314 + 315 + ```{r} 316 + #| error: true 317 + Square() 318 + ``` 319 + 320 + And since `Square`s are `Rect`s, we get all the same nice things. 321 + 322 + 323 + ```{r} 324 + #| error: true 325 + 326 + s <- Square(10) 327 + s 328 + s@area <- 0 329 + 330 + Square(-9) 331 + ``` 332 + 333 + We could've overwritten `Rect`'s `@area` to exploit our knowledge of `Square`s: 334 + 335 + ```{r} 336 + Square <- new_class("Square", 337 + Rect, 338 + properties = list( 339 + area = new_property(class_numeric, getter = function(self) self@width^2, setter = NULL) 340 + ), 341 + constructor = function(side) { 342 + force(side) 343 + new_object(S7_object(), width = side, height = side) 344 + } 345 + ) 346 + 347 + Square(5) 348 + 349 + Square@properties$area$getter 350 + ``` 351 + 352 + There is certainly a lot more to learn about properties and classes, but this should be enough to make you dangerous. The website (and of course `BigNum`) are great places to see and learn more! 353 + 354 + ## Generics and Methods 355 + 356 + [Website](https://rconsortium.github.io/S7/articles/generics-methods.html) 357 +