···2233Written for `S7 0.2.0.`
4455-```r
55+``` r
66pak::pkg_install("VisruthSK/BigNum")
77```
88···39394040S7 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:
41414242-```r
4242+``` r
4343x <- 1:10
4444class(x) <- "test"
4545mean.test <- function(x){
···52525353I 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:
54545555-```r
5555+``` r
5656mean.test <- function(x){
5757 "HELLO WORLD"
5858}
···64646565S7 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.
66666767-```r
6767+``` r
6868library(S7)
69697070Shape <- new_class("Shape", abstract = TRUE)
···97979898It can easily be extended to admit a `Square` class:
9999100100-```r
100100+``` r
101101Square <- new_class("Square",
102102 Rect,
103103 constructor = function(side) {
···111111112112And, 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.
113113114114-```r
114114+``` r
115115positive_numeric <- new_property(class_numeric,
116116 validator = function(value) {
117117 if (value <= 0) "must be greater than 0"
···150150151151We could also compute area in a property instead of a method:
152152153153-```r
153153+``` r
154154positive_numeric <- new_property(class_numeric,
155155 validator = function(value) {
156156 if (value <= 0) "must be greater than 0"
···215215216216##### Read Only Properties
217217218218-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.
218218+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.
219219220220-```r
220220+``` r
221221# https://rconsortium.github.io/S7/articles/classes-objects.html#frozen-properties
222222eg <- new_class("eg",
223223 properties = list(
···243243244244You can, of course, bypass this:
245245246246-```r
246246+``` r
247247attr(tmp, "VALUE") <- 1
248248tmp
249249#> <eg>
···258258259259I don't think you can't easily do anything like this in S7 ([yet](https://github.com/RConsortium/S7/issues/515)):
260260261261-```r
261261+``` r
262262function(...){
263263 structure(..., class = "test")
264264}
···279279So 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!**
280280281281I 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 :)
282282+283283+## Usage
284284+285285+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.
282286283287## Acknowledgements
284288···306310307311- Write tests
308312309309-- Write a vignette?
313313+- Write a vignette?
···11+---
22+title: "BigNum"
33+output: rmarkdown::html_vignette
44+vignette: >
55+ %\VignetteIndexEntry{BigNum}
66+ %\VignetteEngine{knitr::rmarkdown}
77+ %\VignetteEncoding{UTF-8}
88+---
99+1010+```{r, include = FALSE}
1111+knitr::opts_chunk$set(
1212+ collapse = TRUE,
1313+ comment = "#>"
1414+)
1515+```
1616+1717+```{r setup}
1818+library(BigNum)
1919+```
2020+2121+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.
2222+2323+<!-- ## Getting Started -->
2424+2525+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.
2626+2727+```{r}
2828+library(S7)
2929+3030+Shape <- new_class("Shape")
3131+```
3232+3333+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.
3434+3535+```{r}
3636+Shape # class
3737+```
3838+3939+To get *a* `Shape`, we can call the constructor. Since we haven't given any data to `Shape`, the constructor is an empty function.
4040+4141+```{r}
4242+Shape() # object
4343+```
4444+4545+Let us add some data. Let's say that all `Shape`s should store their name, as a character vector.
4646+4747+```{r}
4848+Shape <- new_class("Shape", properties = list(name = class_character))
4949+```
5050+5151+::: {.callout-note collapse="true"}
5252+## Subjective Style Note
5353+This note can be ignored as it pertains only to style.
5454+5555+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.
5656+:::
5757+5858+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.
5959+6060+We access properties using the `@` syntax, e.g. `object@property`.
6161+6262+```{r}
6363+square <- Shape("square")
6464+square@name
6565+```
6666+6767+It is important to define the correct property type as R will now assert that the property matches the defined type.
6868+6969+```{r}
7070+#| error: true
7171+Shape(10)
7272+```
7373+7474+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.
7575+7676+[^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.
7777+7878+```{r}
7979+Shape@constructor
8080+```
8181+8282+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.
8383+8484+```{r}
8585+Shape <- new_class("Shape",
8686+ properties = list(
8787+ name = class_character,
8888+ sides = class_integer
8989+ )
9090+)
9191+```
9292+9393+::: {.callout-note collapse="true"}
9494+## Subjective Style Note
9595+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.
9696+:::
9797+9898+Properties have default values. We can observe them by creating an empty `Shape`:
9999+100100+```{r}
101101+Shape()
102102+```
103103+104104+or by looking at the default values in the constructor:
105105+106106+```{r}
107107+Shape@constructor
108108+```
109109+110110+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:
111111+112112+```{r}
113113+Circle <- new_class("Circle",
114114+ Shape,
115115+ properties = list(
116116+ radius = class_numeric
117117+ )
118118+)
119119+```
120120+121121+`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]
122122+123123+```{r}
124124+class(Shape())
125125+class(Circle())
126126+```
127127+128128+Inheritance works as you might expect: Circles now have all the methods of Shapes, as well as `@radius`.
129129+130130+Think about if the following would return an error, and why. (Hint: check the class definition again.)
131131+132132+```{r}
133133+#| eval: false
134134+Circle("circle", 1, 5)
135135+```
136136+137137+We defined `@sides` to be an integer, and there is no type coercion, so `1`, a double, won't be accepted.
138138+139139+```{r}
140140+#| error: true
141141+Circle("circle", 1, 5)
142142+```
143143+144144+So we must pass an integer number of sides.
145145+146146+```{r}
147147+my_circle <- Circle("circle", 1L, 5)
148148+my_circle
149149+```
150150+151151+Observe that the default constructor places the properties of the parent class before those of the children.
152152+153153+```{r}
154154+Circle
155155+Circle@constructor
156156+```
157157+158158+You can change this easily using a custom constructor.
159159+160160+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.
161161+162162+```{r}
163163+Circle("circle", -1L, -10)
164164+```
165165+166166+This is obviously not desirable. We can restrict this behavior by creating a custom property.
167167+168168+```{r}
169169+positive_numeric <- new_property(class_numeric,
170170+ validator = function(value) {
171171+ if (value <= 0) "must be greater than 0"
172172+ }
173173+)
174174+```
175175+176176+`validator()` is
177177+178178+<!-- https://rconsortium.github.io/S7/articles/classes-objects.html#validation-1 -->
179179+180180+[^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)`).