--- title: "Comparison to R6" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{versus_r6} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", error = TRUE ) ``` Q7 is expected to be compared with R6, the premier object facility in R. Q7 covers the majority of R6 capabilities. The main difference is that Q7 promotes compositional object construction, instead of hereditary. - Compositonal: I put a plasma cannon on my spaceship, then expanded the cargo area. - Hereditary: I made my spaceship a special kind of spaceship that has a plasma cannon, then made it more special with an expanded cargo area. #### Terminologies The blueprint for an _object_: - R6: a class - Q7: a type The object which creates instances from the blueprint - R6: a generator - within, a `$new()` method - Q7: a constructor function Functions defined inside an object - R6: a method - Q7: - a bound function (as opposed to a free function) - a domestic function (as opposed to a foreign function) The following is the equivalent to examples from R6's [Introduction](https://r6.r-lib.org/articles/Introduction.html), leaving out original comments & explainations. You can compare the the implementation of R6 and Q7 side-by-side. ```{r} library(Q7) ``` ### [Basics](https://r6.r-lib.org/articles/Introduction.html#basics) ```{r} Person <- type(function(name, hair){ name <- name hair <- hair set_hair <- function(val){ hair <<- val } greet <- function(){ cat(paste0("Hello, my name is ", name, ".\n")) } }, "Person") Person ``` ```{r} ann <- Person("Ann", "black") ann ``` ```{r} ann$hair ann$greet() ann$set_hair("red") ann$hair ``` ### [Private members](https://r6.r-lib.org/articles/Introduction.html#private-members) ```{r} Queue <- type(function(...){ private[queue] <- list() private[length] <- function(){ base::length(queue) } add <- function(x){ queue <<- c(queue, list(x)) invisible(.my) } remove <- function() { if (length() == 0) return(NULL) head <- queue[[1]] queue <<- queue[-1] head } private[dots] <- list(...) # this is necessary because ... (dot-dot-dot) must be captured here, and that # the initialize() function must not take any arguments. private[initialize] <- function(){ for (item in dots) { add(item) } } }) q <- Queue(5, 6, "foo") ``` ```{r} q$add("something") q$add("another thing") q$add(17) q$remove() q$remove() ``` ```{r} q$queue q$length() ``` ```{r} q$add(10)$add(11)$add(12) ``` ```{r} q$remove() q$remove() q$remove() q$remove() ``` ### [Active Bindings](https://r6.r-lib.org/articles/Introduction.html#active-bindings) ```{r} Numbers <- type(function(){ x <- 100 active[x2] <- function(value) { if (missing(value)) return(x * 2) else x <<- value/2 } active[rand] <- function(){ rnorm(1) } }, "Numbers") n <- Numbers() n$x n$x2 n$x2 <- 1000 n$x n$rand n$rand n$rand <- 3 ``` ```{r} HistoryQueue <- Queue %>% implement({ head_idx <- 0 show <- function() { cat("Next item is at index", head_idx + 1, "\n") for (i in seq_along(queue)) { cat(i, ": ", queue[[i]], "\n", sep = "") } } remove <- function() { if (length() - head_idx == 0) return(NULL) head_idx <<- head_idx + 1 queue[[head_idx]] } }) hq <- HistoryQueue(5, 6, "foo") hq$show() hq$remove() hq$show() hq$remove() ``` NOTE: There is no inheritance in Q7, so you cannot call methods of your parent class. But you can rename anything you don't meant to override. ```{r} CountingQueue <- Queue %>% implement({ private[total] <- 0 private[proto.add] <- add add <- function(x) { total <<- total + 1 proto.add(x) } get_total <- function() total }) cq <- CountingQueue("x", "y") cq$get_total() cq$add("z") cq$remove() cq$remove() cq$get_total() ``` ### [Fields containing reference objects](https://r6.r-lib.org/articles/Introduction.html#fields-containing-reference-objects) ```{r} SimpleClass <- type(function(){ x <- NULL }, "SimpleClass") SharedField <- type(function(){ e <- SimpleClass() }, "SharedField") s1 <- SharedField() s1$e$x <- 1 s2 <- SharedField() s2$e$x <- 2 s1$e$x ``` Q7 and R6 again show differnet behavior. In Q7's case, `s1`'s `x` isn't changed with that of `s2`. The `x` in the R6 example lives with the generator; the `x` in Q7 lives with the instance. The R6 example goes on to show a solution with an separate initializer; the same this not necessary in Q7, as the type definition itself is its initializer(a separate `initialize()` subroutine can be defined to run once at an object's initialization). ### [Other topics](https://r6.r-lib.org/articles/Introduction.html#other-topics) #### Adding members to an existing class ```{r} Simple <- type(function(){ x <- 1 getx <- function(){ x } }, "Simple") Simple <- Simple %>% implement({ getx2 <- function(){ x * 2 } }) Simple <- Simple %>% implement({ x <- 10 }) s <- Simple() s$getx2() ``` In Q7, new code is simply appened to the old, meaning everything will be executed linearly from the beginning to the end. This make it inefficient when you replace something costly to make, like reading in a large amount of data or performing a lengthy calculation. In this case, it's best to make a new type from scratch, or define a common prototype without the costly members. Q7 type constructors need not (and cannot) be locked. #### Cloning Objects ```{r} Simple <- type(function(){ x <- 1 getx <- function(){ x } }, "Simple") s <- Simple() s1 <- clone(s) s1$x <- 2 s1$getx() s$getx() ``` __Deep Cloning__ ```{r} Simple <- type(function(){ x <- 1 }, "Simple") Cloneable <- type(function(){ s <- NULL s <- Simple() }, "Cloneable") c1 <- Cloneable() c2 <- clone(c1) c1$s$x <- 2 c2$s$x ``` The default `clone()` behavior in Q7 is deep (recursive). So any nested instances also gets cloned. Like in R6, only object instances will be cloned deeply. The example of a custom `deep_clone` method in the R6 document is skipped for brevity. #### Printing Q7 objects to the screen ```{r} prettyCountingQueue <- type(function(...){ extend(CountingQueue)(...) print <- function(){ cat(" of ", get_total(), " elements\n", sep = "") } }, "prettyCountingQueue") pq <- prettyCountingQueue(1, 2, "foobar") pq ``` #### Finalizers ```{r} A <- type(function(){ private[finalize] <- function(.my){ base::print("Finalizer has been called!") # Must always qualify `print()` with package name `base`, # because it is masked by`print()` in the object masks } }) obj <- A() rm(obj); gc() ``` For the finalizer function, you must define an argument (`.my`, but could be any name) to represent the object itself. #### Class methods vs. member functions In Q7 context, _domestic functions vs foreign functions_ ```{r} FunctionWrapper <- type({ fn <- NULL get_my <- function(){ .my } }) a <- FunctionWrapper() .my <- 100 a$fn <- function(){ .my } a$get_my() a$fn() ``` ```{r} b <- clone(a) b$get_my() b$fn() ```