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.

Started work on vignette

+204 -12
+1
.gitignore
··· 48 48 # RStudio Connect folder 49 49 rsconnect/ 50 50 .Rproj.user 51 + inst/doc
+3
DESCRIPTION
··· 16 16 Roxygen: list(markdown = TRUE) 17 17 RoxygenNote: 7.3.2 18 18 Suggests: 19 + knitr, 20 + rmarkdown, 19 21 Rmpfr, 20 22 testthat (>= 3.0.0) 21 23 Config/testthat/edition: 3 22 24 Config/testthat/parallel: true 25 + VignetteBuilder: quarto
+16 -12
README.md
··· 2 2 3 3 Written for `S7 0.2.0.` 4 4 5 - ```r 5 + ``` r 6 6 pak::pkg_install("VisruthSK/BigNum") 7 7 ``` 8 8 ··· 39 39 40 40 S7 marries (most of) the flexibility of S3 with (most of) the formality of S4. I find it strikes a perfect middle ground which makes it easier to dictate and understand what objects of type `<T>` can and can't do. S7 is elegantly formal. I first encountered S3 when speaking to Dr Bodwin; the topic came up somehow and she showed me how simple and flexible S3 is with an example, something like: 41 41 42 - ```r 42 + ``` r 43 43 x <- 1:10 44 44 class(x) <- "test" 45 45 mean.test <- function(x){ ··· 52 52 53 53 I was shocked at how easy it was to defined a class and write its method for a generic, especially since my knowledge of OOP was mostly in Java (which is very verbose). The flexibility offers a lot of obvious benefits, but it does come with some drawbacks. Firstly, I think it can be a bit hard to understand; I still find S3 object construction a bit strange. For example, if I wanted to skip `x`'s declaration, I could've written instead: 54 54 55 - ```r 55 + ``` r 56 56 mean.test <- function(x){ 57 57 "HELLO WORLD" 58 58 } ··· 64 64 65 65 S7 may be formal, but it is also elegant–there are nice design patterns that can be implemented effortlessly. Take, for example, this implementation of some `Shape`s. 66 66 67 - ```r 67 + ``` r 68 68 library(S7) 69 69 70 70 Shape <- new_class("Shape", abstract = TRUE) ··· 97 97 98 98 It can easily be extended to admit a `Square` class: 99 99 100 - ```r 100 + ``` r 101 101 Square <- new_class("Square", 102 102 Rect, 103 103 constructor = function(side) { ··· 111 111 112 112 And, more usefully, our classes can be (minimally) changed to guarantee that the provided values are positive–being numeric is already ensured by using `class_numeric`, even in the original definitions. 113 113 114 - ```r 114 + ``` r 115 115 positive_numeric <- new_property(class_numeric, 116 116 validator = function(value) { 117 117 if (value <= 0) "must be greater than 0" ··· 150 150 151 151 We could also compute area in a property instead of a method: 152 152 153 - ```r 153 + ``` r 154 154 positive_numeric <- new_property(class_numeric, 155 155 validator = function(value) { 156 156 if (value <= 0) "must be greater than 0" ··· 215 215 216 216 ##### Read Only Properties 217 217 218 - You can set read only properties with S7, which you can observe in the package (properties in ALLCAPS are constants or "final".) That is a very nice way to expose values which you wish users to be aware of but don't want to mess with, like the value of a linked list node. 218 + You can set read only properties with S7, which you can observe in the package (properties in ALLCAPS are constants or "final".) That is a very nice way to expose values which you wish users to be aware of but don't want to mess with, like the value of a linked list node. 219 219 220 - ```r 220 + ``` r 221 221 # https://rconsortium.github.io/S7/articles/classes-objects.html#frozen-properties 222 222 eg <- new_class("eg", 223 223 properties = list( ··· 243 243 244 244 You can, of course, bypass this: 245 245 246 - ```r 246 + ``` r 247 247 attr(tmp, "VALUE") <- 1 248 248 tmp 249 249 #> <eg> ··· 258 258 259 259 I don't think you can't easily do anything like this in S7 ([yet](https://github.com/RConsortium/S7/issues/515)): 260 260 261 - ```r 261 + ``` r 262 262 function(...){ 263 263 structure(..., class = "test") 264 264 } ··· 279 279 So why BigNum specifically? Well, mostly because it's easy to implement. The BigNum project is taken from my CSC 203 class, an OOP course at Cal Poly. I already had all the methods implemented (in Java) and a clear idea of what I needed to do and how, with the main work being in porting design to R as opposed to novel thought. This greatly simplified dev time since I had a reference implementation to use. **Importantly,** **this (along with my lack of experience) could lead to unidiomatic R/S7 code and design patterns.** **If you notice anything strange, please open an issue/PR!** 280 280 281 281 I haven't developed an R package before, and so that provided additional motivation for me to create this project. That also means that this package is certainly written sub-optimally. Additionally, my experience with S7 is extremely limited--I would be extremely grateful for any and all R sourcerers who can rain issues and pull requests down from the heavens fixing all my mistakes :) 282 + 283 + ## Usage 284 + 285 + I would like to stress that this is a toy package. It is functional, and you can do some things with `big_num`s (viz. construct them from strings, multiply, add, and exponentiate them), but the algorithms implemented are naive, but accurate. I envision this project being most useful as a companion to the website, extending the examples presented there in a cohesive package context. 282 286 283 287 ## Acknowledgements 284 288 ··· 306 310 307 311 - Write tests 308 312 309 - - Write a vignette? 313 + - Write a vignette?
+4
vignettes/.gitignore
··· 1 + *.html 2 + *.R 3 + 4 + /.quarto/
+180
vignettes/BigNum.Rmd
··· 1 + --- 2 + title: "BigNum" 3 + output: rmarkdown::html_vignette 4 + vignette: > 5 + %\VignetteIndexEntry{BigNum} 6 + %\VignetteEngine{knitr::rmarkdown} 7 + %\VignetteEncoding{UTF-8} 8 + --- 9 + 10 + ```{r, include = FALSE} 11 + knitr::opts_chunk$set( 12 + collapse = TRUE, 13 + comment = "#>" 14 + ) 15 + ``` 16 + 17 + ```{r setup} 18 + library(BigNum) 19 + ``` 20 + 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 + 23 + <!-- ## Getting Started --> 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. 26 + 27 + ```{r} 28 + library(S7) 29 + 30 + Shape <- new_class("Shape") 31 + ``` 32 + 33 + This defines a `Shape` class which inherits from the base S7 object and doesn't have any data associated with it. The first argument to `new_class()` as specified in the docs (the S7 website) should be the name of the class as a string. Note that we have only defined a *constructor* and don't have an instance of it. 34 + 35 + ```{r} 36 + Shape # class 37 + ``` 38 + 39 + To get *a* `Shape`, we can call the constructor. Since we haven't given any data to `Shape`, the constructor is an empty function. 40 + 41 + ```{r} 42 + Shape() # object 43 + ``` 44 + 45 + Let us add some data. Let's say that all `Shape`s should store their name, as a character vector. 46 + 47 + ```{r} 48 + Shape <- new_class("Shape", properties = list(name = class_character)) 49 + ``` 50 + 51 + ::: {.callout-note collapse="true"} 52 + ## Subjective Style Note 53 + This note can be ignored as it pertains only to style. 54 + 55 + We think that class definitions in general shouldn't be inlined like they are above--however, since it is so short, we think this is an exception. Later we will see longer class definitions and will comment on style again there. 56 + ::: 57 + 58 + We use `properties` in `new_class()` to define what data the class holds. S7 objects have *properties* (similar-ish to S4 `slots`), and are defined through a list. We provide the name of the property: `name`, and the type of data it can hold: `class_character`. `class_character` is part of a special set of properties exported by `{S7}`; other common ones include `class_numeric`, `class_logical`, `class_list`, etc. You can find an exhaustive list [on the website](https://rconsortium.github.io/S7/reference/index.html#compatibility). Defining the class of properties upfront allows for some validation through type safety. 59 + 60 + We access properties using the `@` syntax, e.g. `object@property`. 61 + 62 + ```{r} 63 + square <- Shape("square") 64 + square@name 65 + ``` 66 + 67 + It is important to define the correct property type as R will now assert that the property matches the defined type. 68 + 69 + ```{r} 70 + #| error: true 71 + Shape(10) 72 + ``` 73 + 74 + Here we attempt to make a `Shape` with a `@name`[^1] of `10`, but since `10` isn't a character, we cannot do this. Note that there is no automatic type coercion. By default, we pass properties to the constructor by position, and the order is determined by the order in `properties`. We can observe properties of the class `Shape` itself the same way we look at properties of S7 objects. 75 + 76 + [^1]: We use the `@` symbol (`@property`) to signify that we are talking about a property of some object, just like how you would access it. This is another style thing, and certainly not standard. 77 + 78 + ```{r} 79 + Shape@constructor 80 + ``` 81 + 82 + That is the default constructor--we'll learn how to write a custom constructor later. Let's look at what happens when we add another property. 83 + 84 + ```{r} 85 + Shape <- new_class("Shape", 86 + properties = list( 87 + name = class_character, 88 + sides = class_integer 89 + ) 90 + ) 91 + ``` 92 + 93 + ::: {.callout-note collapse="true"} 94 + ## Subjective Style Note 95 + Note that this class definition is no longer inlined. In general, we think that (excepting the `name` argument of `new_class()`) one should space arguments out with linebreaks. 96 + ::: 97 + 98 + Properties have default values. We can observe them by creating an empty `Shape`: 99 + 100 + ```{r} 101 + Shape() 102 + ``` 103 + 104 + or by looking at the default values in the constructor: 105 + 106 + ```{r} 107 + Shape@constructor 108 + ``` 109 + 110 + Let's add some more `Shape`s, starting with a `Circle`. All Circles are Shapes, so we wish to reflect that in our class definition. We can do that using inheritance, which in S7 is expressed as follows: 111 + 112 + ```{r} 113 + Circle <- new_class("Circle", 114 + Shape, 115 + properties = list( 116 + radius = class_numeric 117 + ) 118 + ) 119 + ``` 120 + 121 + `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 + 123 + ```{r} 124 + class(Shape()) 125 + class(Circle()) 126 + ``` 127 + 128 + Inheritance works as you might expect: Circles now have all the methods of Shapes, as well as `@radius`. 129 + 130 + Think about if the following would return an error, and why. (Hint: check the class definition again.) 131 + 132 + ```{r} 133 + #| eval: false 134 + Circle("circle", 1, 5) 135 + ``` 136 + 137 + We defined `@sides` to be an integer, and there is no type coercion, so `1`, a double, won't be accepted. 138 + 139 + ```{r} 140 + #| error: true 141 + Circle("circle", 1, 5) 142 + ``` 143 + 144 + So we must pass an integer number of sides. 145 + 146 + ```{r} 147 + my_circle <- Circle("circle", 1L, 5) 148 + my_circle 149 + ``` 150 + 151 + Observe that the default constructor places the properties of the parent class before those of the children. 152 + 153 + ```{r} 154 + Circle 155 + Circle@constructor 156 + ``` 157 + 158 + You can change this easily using a custom constructor. 159 + 160 + Whilst we have some validation to ensure our properties are sensible since `@radius` and `@sides` have to be integers and numerics respectively, we can pass negative values. 161 + 162 + ```{r} 163 + Circle("circle", -1L, -10) 164 + ``` 165 + 166 + This is obviously not desirable. We can restrict this behavior by creating a custom property. 167 + 168 + ```{r} 169 + positive_numeric <- new_property(class_numeric, 170 + validator = function(value) { 171 + if (value <= 0) "must be greater than 0" 172 + } 173 + ) 174 + ``` 175 + 176 + `validator()` is 177 + 178 + <!-- https://rconsortium.github.io/S7/articles/classes-objects.html#validation-1 --> 179 + 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)`).