···26262727### Why S7?
28282929-This package was created for a number of reasons. Chief among those is to provide a worked example of S7 in a package context, but it was also written to purvey the benefits of S7, and posit it as the next R OOP paradigm you should reach for. I highly recommend listening to [Hadley's talk on S7 (then called R7) at posit::conf(2022)](https://www.youtube.com/watch?v=P3FxCvSueag) (thank you [Dr Bodwin](https://www.kelly-bodwin.com/) for showing this to me!), and reading the S7 webpage to get arguments for S7 from very bright people. This section can be skipped--or at least skimmed--if you (like me) are convinced that S7 is the way to go.
2929+This package was created for a number of reasons. Chief among those is to provide a worked example of S7 in a package context, but it was also written to purvey the benefits of S7, and posit it as the next R OOP paradigm you should reach for. I highly recommend listening to [Hadley's talk on S7 (then called R7) at posit::conf(2022)](https://www.youtube.com/watch?v=P3FxCvSueag) (thank you [Dr Bodwin](https://www.kelly-bodwin.com/) for showing this to me!), and reading the S7 webpage to get arguments for S7 from very bright people. This section can be skipped--or at least skimmed--if you (like me) are convinced that S7 is the way to go. Read the vignette for a gentle introduction to S7.
30303131Here's an unordered list of some nice things about S7. I won't directly address these ideas, but you can observe them throughout the code.
3232
+1-1
man/BigNum-package.Rd
···99A toy package designed to showcase and serve as a reference/tutorial for S7 development. To be used in conjunction with the S7 package website.
1010}
1111\author{
1212-\strong{Maintainer}: Visruth Srimath Kandali \email{visruth@gmail.com} (\href{https://orcid.org/0009-0005-9097-0688}{ORCID})
1212+\strong{Maintainer}: Visruth Srimath Kandali \email{public@visruth.com} (\href{https://orcid.org/0009-0005-9097-0688}{ORCID})
13131414}
1515\keyword{internal}
+29-23
vignettes/BigNum.Rmd
···11---
22-title: "BigNum"
22+title: "A (Hopefully Gentle) Introduction to S7"
33output: rmarkdown::html_vignette
44vignette: >
55 %\VignetteIndexEntry{BigNum}
···1515```
16161717```{r setup}
1818-library(BigNum)
1918library(S7)
2019```
21202222-This vignette is written to get you acquainted with `{BigNum}` and S7, and assumes no knowledge of the latter; however, this does assume basic general knowledge of objected oriented principles and terminology. I highly recommend that you use this package in conjunction with the [S7 website](https://rconsortium.github.io/S7/), but ideally you should be able to gain a basic understanding of S7 solely through this vignette. If you are completely new to S7, start with this vignette, check the website as you go (especially if I don't explain something to your liking), and once you've read a section or understood a concept, check the source code of `BigNum` to see some (ideally less trivial) examples.
2121+This vignette is written to get you acquainted with `{BigNum}` and S7, and assumes no knowledge of the latter; however, this does assume basic general knowledge of objected oriented principles and terminology. I highly recommend that you use this package in conjunction with the [S7 website](https://rconsortium.github.io/S7/), but ideally you should be able to gain a basic understanding of S7 solely through this vignette. If you are completely new to S7, start with this vignette, check the website as you go (especially if I don't explain something to your liking), and once you've read a section or understood a concept, check the source code of `{BigNum}` to see some (ideally less trivial) examples.
2222+2323+It may be paced too slowly, in which case feel free to skip portions you find trivial. This package and the S7 website tend to have more fleshed out examples and more details.
2324<!-- We will primarily be using examples from the package. -->
24252526## Classes, Properties, & Objects
···75767677Here 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.
77787878-[^1]: I use the `@` symbol (`@property`) to signify that I am talking about a property of some object, just like how you would access it. This is another style thing, and certainly not standard.
7979+[^1]: I use the `@` symbol (`@property`) to signify that I am talking about a property of some object, just like how you would access it. This is another style thing, and is not standard.
79808081```{r}
8182Shape@constructor
···152153)
153154```
154155155155-`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]
156156+`new_class()` has a `parent` argument which defaults to `S7_object` which is the parent class of all S7 objects (think `Object` in Java.) We can see this using `class()`[^2]
156157157158[^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)`).
158159···195196Observe that the default constructor places the properties of the parent class before those of the children.
196197197198```{r}
198198-Circle
199199Circle@constructor
200200```
201201202202-You can change this easily using a custom constructor.
202202+You can change this easily using a custom constructor, which we will see later.
203203204204-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.
204204+Whilst we have some validation to ensure our properties are sensible since `@radius` and `@sides` have to be integers and numerics respectively, we can still pass negative values.
205205206206```{r}
207207Circle("circle", -1L, -10)
···217217)
218218```
219219220220-`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.
220220+`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; returning a string rejects a value. `positive_numeric` is defined to only accept positive numerics, and provides an appropriate error message if faced with negative numbers.
221221222222```{r}
223223Shape <- new_class("Shape",
···240240```
241241242242243243-Before we see how our new classes have appropriate and intuitive behavior, note that I inlined a definition of a custom property analogous to `positive_numeric` but restricted to `integer`s, for `@sides`. I 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.
243243+Before we see how our new classes have appropriate and intuitive behavior, note that I inlined a definition of a custom property analogous to `positive_numeric` but restricted to `integer`s, for `@sides`. I 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. Maybe that is better still, though--I'm torn.
244244245245```{r}
246246#| error: true
···252252Circle("circle", 1L, -10)
253253```
254254255255-Note that the property name is automatically added to the validator's error message. Let us further explore properties.
255255+Note that the property name (e.g. `@radiues` or `@sides`) is automatically added to the validator's error message. Don't hardcode that yourself.
256256+257257+Let us further explore properties.
256258257259```{r}
258260positive_numeric <- new_property(class_numeric,
···271273)
272274```
273275274274-Here we've included what we've written so far. I used `abstract = TRUE` to tell S7 that we don't ever mean to make a `Shape` object outright. This means the following is now not disallowed:
276276+Here we've included what we've written so far. I used `abstract = TRUE` to tell S7 that we don't ever mean to make a `Shape` object outright. This means the following is now disallowed:
275277 <!-- with a small change to `Shape` to disallow instantiation of arbitrary `Shape`s. -->
276278277279```{r}
···291293)
292294```
293295294294-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. I advocate for explicitly setting the setter to `NULL` to make it very clear that `@area` is a read only property.
296296+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` an immutable property. I advocate for explicitly setting the setter to `NULL` to make it very clear that `@area` is a read only property.
295297296298```{r}
297299#| error: true
···304306circ@area <- 0
305307```
306308307307-Circles are getting boring; let's add Rectangles and Squares!
309309+Circles are getting boring; let's add Rectangles and Squares.
308310309311```{r}
310312Rect <- new_class("Rect",
···324326Rect(10, 10)
325327```
326328327327-We can easily extend our shapes to further include `Square`s.
329329+And now we can easily extend our shapes to further include `Square`s.
328330329331```{r}
330332Square <- new_class("Square",
···336338)
337339```
338340339339-Here, we are leveraging the idea that all squares are rectangles to offload most of the heavy work to `Rect`. We are essentially presenting `Square` as an special 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. vWe can set default values of the object in the constructor itself, or could pass a default value in the property definition. 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). Another important thing to note is that if you define a custom constructor, ["any subclass will also require a custom constructor"](https://rconsortium.github.io/S7/articles/classes-objects.html#constructors).
341341+Here, we are leveraging the idea that all squares are rectangles to offload most of the heavy work to `Rect`. We are essentially presenting `Square` as an special 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. We can set default values of the object in the constructor itself, or could pass a default value in the property definition.
342342+343343+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). Another important thing to note is that if you define a custom constructor, ["any subclass will also require a custom constructor"](https://rconsortium.github.io/S7/articles/classes-objects.html#constructors).
340344341345```{r}
342346Square@constructor
···350354Square()
351355```
352356353353-And since `Square`s are `Rect`s, we get all the same nice things `Rect`s do.
357357+Since `Square`s are `Rect`s, we get all the same nice things `Rect`s do.
354358355359```{r}
356360#| error: true
···383387384388Note that we can't use `side` in the definition for `@area` since `side` isn't a property--it's just the name we gave to the only argument we take when creating `Square`s.
385389386386-There is certainly a lot more to learn about properties and classes, but hopefully this has helped you build enough of an understanding of how these constructs work to be able to reason about them. The website (and of course `BigNum`) are great places to see examples and learn more!
390390+There is certainly a lot more to learn about properties and classes, but hopefully this has helped you build enough of an understanding of how these constructs work to be able to reason about them. The website (and of course `{BigNum}`) are great places to see examples and learn more.
387391388392## Generics and Methods
389393···455459}
456460```
457461458458-The arguments to `new_generic()` are relatively straightforward. We start with the name of the method, which should always be the same as the name you assign the value of `new_generic()` to, just like with Classes. The second argument are the dispatch arguments. S7 uses multiple dispatch, which means that multiple objects can be used to determine the correct method for a given generic. `BigNum` uses multiple dispatch (see definition of operators), but for this example we will rely on single dispatch. We pass the title of the argument(s) we wish to dispatch on. We aren't passing the dots here, and [read the website](https://rconsortium.github.io/S7/articles/generics-methods.html#generic-method-compatibility) to see what that implies.
462462+The arguments to `new_generic()` are relatively straightforward. We start with the name of the method, which should always be the same as the name you assign the value of `new_generic()` to, just like with Classes. The second argument are the dispatch arguments. S7 uses multiple dispatch, which means that multiple objects can be used to determine the correct method for a given generic. `{BigNum}` uses multiple dispatch (see definition of operators), but for this example we will rely on single dispatch. We pass the title of the argument(s) we wish to dispatch on. We aren't passing the dots here, and [read the website](https://rconsortium.github.io/S7/articles/generics-methods.html#generic-method-compatibility) to see what that implies.
459463460464After that comes a method implementation. `method()` is very flexible, and allows you to register methods for a variety of generics: viz. for [an S7 generic, an external generic, an S3 generic, or an S4 generic.](https://rconsortium.github.io/S7/reference/method.html) We specify the generic we are defining a method for, and then the class we are adding behavior to. We implement the actually functionality in a garden variety R function--except it must have the named argument as defined in the generic:
461465···466470}
467471```
468472469469-And now we can calculate the area of `Circle`s!
473473+And now we can calculate the area of `Circle`s.
470474471475```{r}
472476Area(Circle(10))
···528532length(Square(5))
529533```
530534531531-If you need to define behavior for a generic defined in another package, you can use [`new_external_generic()`](https://rconsortium.github.io/S7/reference/new_external_generic.html). Example from that site:
535535+If you need to define behavior for a generic defined in another package, you can use [`new_external_generic()`](https://rconsortium.github.io/S7/reference/new_external_generic.html). Example from the site:
532536533537```{r}
534538median_Shapes <- new_external_generic("stats", "median", "x")
···539543median(Rect(4, 2))
540544```
541545546546+Note that this isn't necessary for `base`, `stats`, etc. functions, but would be if you wish to register a method for a generic defined in another package--i.e. an external generic. This example is just to let you see the syntax and usage.
547547+542548Since S7 is built atop of S3, you could do something like this:
543549544550```{r}
···550556551557but I strongly recommend that you use `method()` instead.
552558553553-There are far more details on the website's [Generics and Methods page](https://rconsortium.github.io/S7/articles/generics-methods.html), give it a read!
559559+There are far more details on the website's [Generics and Methods page](https://rconsortium.github.io/S7/articles/generics-methods.html).
554560555561## Conclusion
556562557557-Equipped with this knowledge of S7, you should hopefully be able to understand how the package and S7 works--or at the very least, know enough to know what to search or learn. You should probably be able to understand how the package works, which I hope is a more interesting (and certainly more fleshed out) example than the `Shape`s we looked at!563563+Equipped with this knowledge of S7, you should hopefully be able to understand how this package and S7 works--or at the very least, know enough to know what to search or learn. I'd suggest reading through the source code of the package next, which I hope is a more interesting (and certainly more fleshed out) example than the `Shape`s we looked at. Additionally, the code in the package is slightly different as it is in a package, unlike the examples shown.