···11# Generated by roxygen2: do not edit by hand
2233+export(big_num)
34if (getRversion() < "4.3.0") importFrom("S7", "@")
45import(S7)
56importFrom(rlang,env)
-4
R/BigNum-package.R
···66#' @importFrom rlang env
77## usethis namespace: end
88NULL
99-1010-# enable usage of <S7_object>@name in package code
1111-#' @rawNamespace if (getRversion() < "4.3.0") importFrom("S7", "@")
1212-NULL
+123-50
R/big_num.R
···11-node_or_na <- function(value) is.na(value) || S7_inherits(value, node) # predicate to check if `nd` is a node or NA
11+# Start by looking at the `big_num` class
22+33+node_or_na <- function(value) is.na(value) || S7_inherits(value, node) # predicate to check if `value` is a `node` or `NA`, pulled out to reduce duplication
2435node <- new_class("node",
46 package = "BigNum",
57 properties = list(
68 VALUE = new_property(
77- class_numeric, # TODO: make this a generic? Swap to integers here?
99+ class_numeric, # TODO: make this a generic?
810 getter = function(self) self@VALUE
911 ),
1012 e = new_property(class_environment, getter = function(self) self@e),
···2022 }
2123 )
2224 ),
2323- # TODO: fix this validator too
2425 validator = function(self) {
2526 if (!node_or_na(self@nxt)) {
2627 "@nxt must be a `node` object or `NA`."
2727- }
2828- if (!is.numeric(self@VALUE) || length(self@VALUE) != 1) {
2828+ } else if (!is.numeric(self@VALUE) || length(self@VALUE) != 1) {
2929 "@VALUE must be a `numeric` vector of length 1."
3030 }
3131 },
3232 constructor = function(VALUE, nxt = NA) {
3333 force(VALUE)
3434- # if (VALUE != floor(VALUE)) warning("Coercing value to integer")
3535- VALUE <- as.integer(VALUE)
3634 force(nxt)
3737- new_object(S7_object(), VALUE = VALUE, e = rlang::env(nxt = nxt))
3535+3636+ new_object(S7_object(), VALUE = VALUE, e = rlang::new_environment(list(nxt = nxt)))
3837 }
3938)
4039···4645 getter = function(self) self@e$head,
4746 setter = function(self, value) {
4847 # TODO: kick up to error?
4949- warning("@head should not be set manually", call. = FALSE)
4848+ warning("@head should not be set manually. Maybe you meant to use `append_to_start()`?", call. = FALSE)
4949+5050+ my_env <- self@e
5151+ my_env$head <- value
50525151- self@e$head <- value
5253 self
5353- }
5454+ },
5555+ validator = node_or_na
5456 ),
5557 tail = new_property(node,
5658 getter = function(self) self@e$tail,
5759 setter = function(self, value) {
5860 warning("@tail should not be set manually", call. = FALSE)
59616060- self@e$tail <- value
6262+ my_env <- self@e
6363+ my_env$tail <- value
6464+6165 self
6266 }
6367 ),
6464- length = new_property(class_numeric, getter = function(self) self@e$length, setter = function(self, value) self@e$length <- value),
6565- e = new_property(class_environment)
6666- ), validator = function(self) {
6767- # TODO: validation might be broken?
6868+ length = new_property(class_integer, getter = function(self) self@e$length, setter = function(self, value) {
6969+ warning("@length should not be set manually", call. = FALSE)
7070+ environ <- self@e
7171+ environ$length <- as.integer(value)
7272+7373+ self
7474+ }),
7575+ e = new_property(class_environment, getter = function(self) self@e)
7676+ ),
7777+ validator = function(self) {
6878 if (!node_or_na(self@head)) {
6979 "@head must be a `node` object or `NA`."
7080 } else if (!node_or_na(self@tail)) {
7181 "@tail must be a `node` object or `NA`."
7282 }
7373- }, constructor = function(num = NULL) {
8383+ },
8484+ constructor = function(num = NULL) {
8585+ force(num)
8686+7487 if (is.null(num) || num == "") {
7575- return(new_object(S7_object(), e = rlang::env(head = NA, tail = NA, length = 0)))
8888+ return(new_object(S7_object(), e = rlang::new_environment(list(head = NA, tail = NA, length = 0))))
7689 }
77907891 extract_digit_to_node <- function(len) node(as.integer(substring(num, len, len))) # TODO: throw error if the char isn't numeric
···8699 }
87100 }
881018989- e <- rlang::env(head = nodes[[1]], tail = nodes[[len]], length = len)
102102+ e <- rlang::new_environment(list(
103103+ head = nodes[[1]],
104104+ tail = nodes[[len]],
105105+ length = len
106106+ ))
901079191- S7::new_object(S7::S7_object(), e = e)
108108+ new_object(S7_object(), e = e)
92109 }
93110)
941119595-#' Makes a BigNum
112112+#' Infinite precision natural number using a singly linked list
113113+#'
114114+#' @description
115115+#' BigNum exposes three S7 objects, which are all closely related,
116116+#' i.e. they are all tightly coupled--which isn't great OOP design
117117+#' but will suffice.
96118#'
9797-#' @param num A character vector representing a number.
119119+#' The `big_num` class is essentially a wrapper around [linked_list]
120120+#' with a custom print method as well as some defined operators such as
121121+#' `+`, `*`, and `^` with an integer.
98122#'
9999-#' @return A big_num S7 object.
100123#' @export
101101-#'
102102-#' @examples
103103-big_num <- new_class("big_num",
104104- package = "BigNum",
124124+#' @param num A string representation of a num.
125125+big_num <- new_class("big_num", # this defines the class, and should match the name of the object on the left-hand-side of the assignment.
126126+ package = "BigNum", # this defines a package for this `big_num` object so that it doesn't conflict with other potential `big_num` objects from other packages.
127127+ # properties defines a list of variables that `big_num`s have. Here we have just one property, a `linked_list`
105128 properties = list(
106106- ll = new_property(linked_list, getter = function(self) self@ll)
129129+ ll = new_property(linked_list, getter = function(self) self@ll) # this property has a custom getter, but no setter. This means that you can get a `big_num`'s `ll` but you cannot set it--i.e. this is a read-only property.
107130 ),
131131+ # the class validator is a function that takes a purported `big_num` object (viz. `self`), and runs some checks to ensure that it actually is a `big_num`.
132132+ validator = function(self) {
133133+ # as expected, this predicate just checks if `self`'s `ll` is an actual `linked_list`
134134+ if (!S7_inherits(self@ll, linked_list)) {
135135+ "@ll must be a valid linked list."
136136+ }
137137+ # all validators should return a string if there is an error, or NULL if all's good. Let R return NULL for you automatically.
138138+ },
139139+ # S7 objects can have custom constructors which allow you to do some logic with inputs before ending with a call to `new_object()`
140140+ # this constructor finesses the input to allow for construction of `big_num`s with a numeric `num` as well as the expected character input
141141+ # note as well that the constructor is a function, and so it takes certain arguments which can have default values. Here, all `big_nums` start
142142+ # out empty. However, looking at the `node` class one can note that `val` has no default and so must be provided when creating a `node` object.
108143 constructor = function(num = "") {
109109- S7::new_object(S7::S7_object(), ll = linked_list(format(num, trim = TRUE, scientific = FALSE)))
144144+ force(num)
145145+ new_object(S7_object(), ll = linked_list(format(num, trim = TRUE, scientific = FALSE)))
110146 }
111147)
112148···118154119155append <- new_generic("append", c("x", "ll"))
120156method(append, list(node, linked_list)) <- function(x, ll) {
121121- suppressWarnings(
157157+ suppressWarnings({
122158 if (is.na(ll@head)) {
123159 ll@head <- x
124160 ll@tail <- x
···126162 ll@tail@nxt <- x
127163 ll@tail <- x
128164 }
129129- )
130130-131131- # TODO: what does this error mean and why does this still work if ignored
132132- # the attributes do exist in the environment so I'm not using rlang::env properly
133133- # Error in `@<-.S7_object`:
134134- # \! Tried to remove non-existent element from pairlist
135135- try(ll@length <- ll@length + 1, silent = TRUE)
165165+ ll@length <- ll@length + 1
166166+ })
136167137168 invisible(ll)
138169}
···146177147178append_to_start <- new_generic("append_to_start", c("x", "ll"))
148179method(append_to_start, list(node, linked_list)) <- function(x, ll) {
149149- suppressWarnings(
180180+ suppressWarnings({
150181 if (is.na(ll@head)) {
151182 ll@head <- x
152183 ll@tail <- x
···154185 x@nxt <- ll@head
155186 ll@head <- x
156187 }
157157- )
158158-159159- try(ll@length <- ll@length + 1, silent = TRUE)
188188+ ll@length <- ll@length + 1
189189+ })
160190161191 invisible(ll)
162192}
···187217 return(invisible(x))
188218 }
189219190190- # TODO: remove leading 0s?
191220 stack <- character(len)
192221 current <- x@ll@head
193222···197226 }
198227199228 string <- paste0(stack, collapse = "")
200200- cat(string)
229229+ cat(string, "\n")
201230202231 invisible(x)
203232}
···243272 big_num(e1) + e2
244273}
245274275275+276276+# function to remove tail zeros from a `big_num`
277277+remove_leading_zeros <- function(bn) {
278278+ ll <- bn@ll
279279+ # TODO: length is 1 less than it should be still
280280+ if (ll@length <= 1) {
281281+ return(bn)
282282+ }
283283+284284+ current <- ll@head
285285+ last_nonzero <- NA
286286+ final_length <- 0
287287+288288+ while (!is.na(current)) {
289289+ if (current@VALUE != 0) {
290290+ last_nonzero <- current
291291+ final_length <- ll@length
292292+ } else {
293293+ final_length <- final_length - 1
294294+ }
295295+ current <- current@nxt
296296+ }
297297+298298+ if (is.na(last_nonzero)) {
299299+ suppressWarnings({
300300+ ll@head@nxt <- NA
301301+ ll@tail <- ll@head
302302+ ll@length <- 1
303303+ })
304304+305305+ return(invisible(bn))
306306+ }
307307+308308+ last_nonzero@nxt <- NA
309309+ suppressWarnings({
310310+ ll@tail <- last_nonzero
311311+ ll@length <- final_length
312312+ })
313313+314314+ invisible(bn)
315315+}
316316+246317`*` <- new_external_generic("base", "*", c("e1", "e2"))
247318method(`*`, list(big_num, big_num)) <- function(e1, e2) {
248319 product <- big_num(0)
···262333 shift2 <- shift2 + 1
263334 }
264335265265- product
336336+ remove_leading_zeros(product)
266337}
267338method(`*`, list(big_num, class_numeric)) <- function(e1, e2) {
268339 e1 * big_num(e2)
···289360}
290361291362`==` <- new_external_generic("base", "==", c("e1", "e2"))
292292-method(`==`, list(big_num, big_num)) <- function(e1, e2) {
293293- len <- e1@ll@length
294294- if (len != e2@ll@length) {
363363+method(`==`, list(linked_list, linked_list)) <- function(e1, e2) {
364364+ len <- e1@length
365365+ if (len != e2@length) {
295366 return(FALSE)
296367 }
297297- node1 <- e1@ll@head
298298- node2 <- e2@ll@head
368368+ node1 <- e1@head
369369+ node2 <- e2@head
299370 for (i in len:1) {
300371 if (node1@VALUE != node2@VALUE) {
301372 return(FALSE)
···305376 }
306377 TRUE
307378}
379379+method(`==`, list(big_num, big_num)) <- function(e1, e2) {
380380+ e1@ll == e2@ll
381381+}
308382method(`==`, list(big_num, class_numeric)) <- function(e1, e2) {
309383 e1 == big_num(e2)
310384}
311385method(`==`, list(class_numeric, big_num)) <- function(e1, e2) {
312386 big_num(e1) == e2
313387}
314314-
+4
R/zzz.R
···11.onLoad <- function(lib, pkg) {
22 S7::methods_register()
33}
44+55+# enable usage of <S7_object>@name in package code
66+#' @rawNamespace if (getRversion() < "4.3.0") importFrom("S7", "@")
77+NULL
+51-7
README.md
···11-# BigNum
11+# BigNum: A showcase and reference for S7
22+33+Written for `S7 0.2.0.`
44+55+## Overview
66+77+***This project is a work in progress, needs reviews and revisions to check for consistency and such. Some best practices for S7 development (e.g. [documentation](https://github.com/RConsortium/S7/issues/315)) haven't been established yet though.***
88+99+What is S7? What is BigNum? Why can't I subtract two `big_num`s?
1010+1111+Let's start with the most interesting part: S7.
1212+1313+### S7
1414+1515+["The S7 package is a new OOP system designed to be a successor to S3 and S4." - R Consortium Object-Oriented Programming Working Group.](https://rconsortium.github.io/S7/)
21633-TODO:
1717+S7 is a new way to do OOP in R, and it is fantastic. That quote links to the official S7 website, which contains a great deal of information regarding S7--its origins, features, compatibility, specifications, etc. Any developer who wishes to use S7 should certainly study that website. It is quite cogent and detailed, and I use it as a reference all the time–its practically an extra chapter of Advanced R. This package is meant to be used in conjunction with the S7 website, hopefully serving as a (slightly non-contrived) total which can be used to understand how S7 can work in practice. Since S7 is so new, and is still technically experimental, major changes to the API aren't unusual. As such, for this project to be effective, it must be kept in time with S7 as the OOP system matures. Best practice, as and when they are developed, should be implemented to serve as an effective, worked out example for S7 package development.
41855-- Finish description
1919+S7 is in use by a number of people already–some are writing about their experiences as well. One I happened upon is this [blog post](https://blog.djnavarro.net/posts/2024-02-25_s7/) by Dr. Navarro detailing how they used S7 to make some of her latest artwork. Like all of Dr Navarro's posts, it is well worth a read.
62077-- Make the BigNum class
2121+### BigNum
82299-- Write tests
2323+So what is BigNum? And why this project?
2424+2525+Firstly, it is important to note that this is a *toy* package. Whilst the package will build, and expose an API one can use to create `big_num` objects, I do not think one *should* use these as infinite precision numbers (for that purpose, look towards packages like [Rmpfr](https://cran.r-project.org/web/packages/Rmpfr/index.html).) This package's implementation is very rough and many properties one would expect from "numbers" are missing–negative numbers, subtraction, division, modulus, etc. are all not implemented. The point of this package is not to veritably implement infinite precision numbers, but rather to show how S7 can be used in a package to make some basic data structures and work with some generics/functions. The package itself is very simple and has a relatively concise and simple implementation. Since this package's purpose is pedagogical, there isn't much point in implementing all the aforementioned features.
2626+2727+So why BigNum specifically? Well, mostly because it's easy to implement. The BigNum project is taken from my CSC 203 class, a course in OOP at Cal Poly. As such, I already had all the methods implemented and a clear idea of what I needed to do and how, with the main work being in porting logic to R as opposed to devising the methods and classes needed. 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!**
2828+2929+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 :)
3030+3131+## References
3232+3333+Vaughan D, Hester J, Kalinowski T, Landau W, Lawrence M, Maechler M, Tierney L, Wickham H (2024). *S7: An Object Oriented System Meant to Become a Successor to S3 and S4*. R package version 0.2.0.9000, <https://github.com/RConsortium/S7>, <https://rconsortium.github.io/S7/>.
3434+3535+Navarro, Danielle. 2024. “Creating New Generative Art Tools in R with Grid, Ambient, and S7.” February 25, 2024. <https://blog.djnavarro.net/posts/2024-02-25_s7/>.
3636+3737+Henry L, Wickham H (2024). *rlang: Functions for Base Types and Core R and 'Tidyverse' Features*. R package version 1.1.4, <https://CRAN.R-project.org/package=rlang>.
3838+3939+Maechler M (2024). *Rmpfr: Interface R to MPFR - Multiple Precision Floating-Point Reliable*. R package version 1.0-0, <https://CRAN.R-project.org/package=Rmpfr>.
4040+4141+Wickham H (2019). *Advanced R* (2nd ed.). CRC Press.
4242+4343+Wickham H, Bryan J (2023). *R Packages* (2nd ed.). O'Reilly Media.
4444+4545+## TODOs:
4646+4747+- Finish readme
4848+4949+ - add more citations
10501151- Write documentation
12521313-- Write a vignette
5353+ - Comment code
14541515-- Comment code
5555+ - Some basic comments for actual use of package
5656+5757+- Write tests
5858+5959+- Write a vignette?
16601761- Website?
+1-1
man/BigNum-package.Rd
···44\name{BigNum-package}
55\alias{BigNum}
66\alias{BigNum-package}
77-\title{BigNum: A S7 Showcase}
77+\title{BigNum: An S7 Showcase}
88\description{
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}
+20
man/big_num.Rd
···11+% Generated by roxygen2: do not edit by hand
22+% Please edit documentation in R/big_num.R
33+\name{big_num}
44+\alias{big_num}
55+\title{Infinite precision natural number using a singly linked list}
66+\usage{
77+big_num(num = "")
88+}
99+\arguments{
1010+\item{num}{A string representation of a num.}
1111+}
1212+\description{
1313+BigNum exposes three S7 objects, which are all closely related,
1414+i.e. they are all tightly coupled--which isn't great OOP design
1515+but will suffice.
1616+1717+The \code{big_num} class is essentially a wrapper around \link{linked_list}
1818+with a custom print method as well as some defined operators such as
1919+\code{+}, \code{*}, and \code{^} with an integer.
2020+}
+12
tests/testthat.R
···11+# This file is part of the standard setup for testthat.
22+# It is recommended that you do not modify it.
33+#
44+# Where should you do additional test configuration?
55+# Learn more about the roles of various files in:
66+# * https://r-pkgs.org/testing-design.html#sec-tests-files-overview
77+# * https://testthat.r-lib.org/articles/special-files.html
88+99+library(testthat)
1010+library(BigNum)
1111+1212+test_check("BigNum")