···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.
32323333-1. Just the right amount of formal
3434-2. Just the right amount of flexible
3333+1. Just the right amount of formality
3434+2. Just the right amount of flexibility
35353. Cogent API
36364. Easy to extend
37375. Classes, properties, validators, etc. give nice guarantees
3838+6. Simple to swap to from S3
38393939-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:
4040+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:
40414142```{r}
4243x <- 1:10
···5960mean(structure(1:10, class = "test"))
6061```
61626262-but I think this is harder to reason about. The idea of just giving a garden variety vector `1:10` a whole class without really defining what that class is, is strange to me. I find it hard sometimes to understand what things are. S7 makes it very clear what things are; I find this easier to reason about. This is something I noted firsthand when swapping to S7 in some Tidyverse packages. Even with well documented code, it can be hard to understand how things work.
6363+but I think this is harder to reason about. The idea of just giving a garden variety vector `1:10` a whole class without really defining what that class is, is strange to me. I find it hard sometimes to understand what S3 objects are and how they interact with methods. S7 makes it very clear what things are; I find this easier to reason about. This is something I noted firsthand when swapping to S7 in some Tidyverse packages. Even with well documented code, it can be hard to understand how things work.
63646465S7 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.
6566···8687It can easily be extended to admit a `Square` class:
87888889```{r}
8989-Square <- new_class("Square", Rect, constructor = function(side) new_object(Square, width = side, height = side))
9090+Square <- new_class("Square", Rect, constructor = function(side) new_object(S7_object(), width = side, height = side))
9091square <- Square(5)
91929293Area(square)
···100101Circle <- new_class("Circle", Shape, properties = list(radius = positive_numeric))
101102Rect <- new_class("Rect", Shape, properties = list(width = positive_numeric, height = positive_numeric))
102103103103-# Rect(0, 4)
104104+Rect(0, 4)
104105#> ! <Rect> object properties are invalid:
105106#> - @width must be greater than 0
106107107107-# Circle(-10)
108108+Circle(-10)
108109#> ! <Circle> object properties are invalid:
109110#> - @radius must be greater than 0
110111111112# and of course this still holds for Squares
112112-# Square(-1)
113113+Square(-1)
113114#> ! <Square> object properties are invalid:
114115#> - @width must be greater than 0
115116#> - @height must be greater than 0
116117```
117118118118-This is hardly the most interesting example (hence the package!), and these aren't the most groundbreaking principles, but having these OOP ideas are very nice. They're presented in a cogent API and allow you to spell out what you need to do: `new_class()`, `new_generic()`, `new_external_generic()`, `method()`, etc. The formality and structure that S7 provides manifests as interpretable, clean code. Most importantly, it is readable code.
119119+We could also compute area in a property instead of a method:
120120+121121+```{r}
122122+positive_numeric <- new_property(class_numeric, validator = function(value) if (value <= 0) "must be greater than 0")
123123+124124+Shape <- new_class("Shape", abstract = TRUE)
125125+Circle <- new_class(
126126+ "Circle",
127127+ Shape,
128128+ properties = list(radius = positive_numeric, area = new_property(class_numeric, getter = function(self) pi * self@radius^2, setter = NULL)),
129129+ constructor = function(radius) new_object(S7_object(), radius = radius)
130130+)
131131+Rect <- new_class(
132132+ "Rect",
133133+ Shape,
134134+ properties = list(width = positive_numeric, height = positive_numeric, area = new_property(class_numeric, getter = function(self) self@width * self@height, setter = NULL)),
135135+ constructor = function(width, height) new_object(S7_object(), width = width, height = height)
136136+)
137137+Square <- new_class("Square", Rect, constructor = function(side) new_object(S7_object(), width = side, height = side))
138138+139139+circ <- Circle(10)
140140+circ
141141+#> <Circle>
142142+#> @ radius: num 10
143143+#> @ area : num 314
144144+145145+circ@area <- 10
146146+#> ! Can't set read-only property <Circle>@area
147147+148148+Rect(10, 3)
149149+#> <Rect>
150150+#> @ width : num 10
151151+#> @ height: num 3
152152+#> @ area : num 30
153153+154154+Square(5)
155155+#> <Square>
156156+#> @ width : num 5
157157+#> @ height: num 5
158158+#> @ area : num 25
159159+```
160160+161161+`Shapes` are hardly the most interesting example (hence the package!), and these aren't the most groundbreaking principles, but having these OOP ideas in R is very nice. They're presented in a cogent API and allow you to spell out what you need to do: `new_class()`, `new_generic()`, `new_external_generic()`, `method()`, etc. This allows formality without being stuffy or verbose. The structure that S7 provides manifests as interpretable, clean code. Most importantly, it is very readable code.
119162120163#### Some Random Thoughts/Interesting Points
121164122165##### Read Only Properties
123166124124-You can set read only properties with S7, which you can observe in the package (ALLCAPS properties 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. You can, of course, bypass this:
167167+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. You can, of course, bypass this:
125168126169```{r}
127170# https://rconsortium.github.io/S7/articles/classes-objects.html#frozen-properties
···147190148191but you clearly have to go out of your way to. Normal usage of `eg` objects, with the exposed API, would ensure that you don't accidentally change constants.
149192150150-You can also do cool stuff with [computed/dynamic properties](https://rconsortium.github.io/S7/articles/classes-objects.html#properties).
193193+You can also do cool stuff with [computed/dynamic properties](https://rconsortium.github.io/S7/articles/classes-objects.html#properties). The last implementation of `Shape`s above uses this to compute the area dynamically–thus allowing the area to change as you adjust the measurements of a shape.
151194152195##### Var Args
153196···161204162205##### State
163206164164-You have to use environments to get state in S7 (again, see package)--but I have it on very strong authority that a stateful S7 is being thought of. Don't quote me on that.
165165-166166-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 what objects of type `<T>` can and can't do.
207207+You have to use environments to get state in S7 (again, see package)--but I have it on strong authority that a stateful S7 is being thought of. Don't quote me on that.
167208168209### BigNum
169210