[How-to] Share content between several R6 instances

May 29, 2018
By

(This article was first published on The R Task Force, and kindly contributed to R-bloggers)

The object oriented system is a nice and powerful way to program and to build softwares and data in R. Yet, it can be a little bit daunting at first, especially if you’ve alway been coding in R, and with functions. In this blogpost, we’ll have a look at {R6}, one of the most downloaded package from the CRAN, which is one of the backbone of a lot of package backends, and specifically answer the question of data sharing across class instances. 

What is {R6}?

In a few words, {R6} is a modern and flexible Object Oriented (“OO”) framework for R.

So yes, your next question might be: what does “object oriented” mean? With OO, you’re creating objects, and inside these objects are data and methods (i.e, function). Running these methods can change the content of the data contained inside the object.

As in any OO system, {R6} has a system of classes and instances of these classes — which is the subject of this post: how can you share content between every instances of a class?

When should we need {R6}?

One main use case is when you need to build an object, and you want to enclose inside this object information, but also perform a series of actions on these information. You could do this by defining functions and objects and use all these objects together. But the idea here is to have something that is enclosed inside the same object, and all the objects from the same class starts with the same content and methods.

R6 can be used to deal with database connection, for example. Inside the R6 object, you could find all the information about the connection, and also methods specifically related to interacting with the database. You can also built documents, which are constructed inside the object.

Here are some examples of packages using an R6-oriented API:

Playing with {R6}

We will create an R6 class to generate HTML and CSS code, in order to have a webpage.

Disclaimer: of course, this R6 class is (really) limited for webpage generation, the idea here is to provide an example, not to write a prod-ready website generator ?

library(R6)
library(glue)
library(purrr)
library(magrittr)
library(htmltools)


WebPage <- R6Class("WebPage", 
                   public = list(
                     name = character(0),
                     head = c("","",""),
                     body = "",
                     style = '", body,"","")
                     }
                   )
)

a <- WebPage$new("index")

a$add_style("body", list("font-family" = "Helvetica", "color" = "#24292e"))
a$add_style("h2", list("font-size" = "3 em", "color" = "#911414", 
                       "text-align" = "center"))
a$add_style("h3", list("font-size" = "1.5 em", "color" = "#2E2E8F", 
                       "text-align" = "center"))


a$add_tag("h2", "Hey there!")
a$add_tag("h3", "You've reached the rtask teams!")
a$add_tag("p", "We are glad to have you here.")
a$add_tag("p", "Enjoying R6 already?")
a





Hey there!

You've reached the rtask teams!

We are glad to have you here.

Enjoying R6 already?

a$view()


a$save(".")

Ok, so let’s cut everything we did into pieces:

WebPage <- R6Class("WebPage", 

Here, we are giving a name to our class. By convention, it should be CamelCase.

                   public = list(
                     name = character(0),
                     head = c("","",""),
                     body = "",
                     style = '", body, "","")
                     }
                   )
)

And finally, our private list. I chose to put concat in here as I didn’t want nor need this function to be accessible outside the object, I could also have put it outside the class.

Several pages, one CSS

It would be better if every instance of the class shared the same CSS, right?

So, here’s the thing: once an object is instantiated, its public objects are sealed: if A and B are instances of the C class, changing something in the public list of A won’t change it in B. And if you change something in C, that won’t “roll down” to the instances of the class. Which is a behavior you could expect: if I change the title of A, I don’t want the title of B to be changed.

But in our case, we need to be able to access the same data from multiple instances: in other word, I want all my instances of WebPages to access the very same CSS code. To do this, we’ll use R environments. In the private field of our R6 class definition, we’ll set up an environment in the `shared` field. Then, everytime I will create a new object of this class, the shared variable will point to the very same environment (and in fine, to the same CSS content).

Let’s do this:

WebSkeleton <- R6Class("WebSkeleton", 
                       public = list(
                         name = character(0),
                         head = c("","",""),
                         body = "",
                         add_style = function(identifier, content){
                           content <- imap_chr(content, ~ glue("{.y} : {.x};")) %>%
                             unname() %>% 
                             paste(collapse = " ") 
                           glued <- glue("%identifier% { %content% }", 
                                         .open = "%", .close = "%")
                           private$shared$style <- c( private$shared$style, glued)
                         },
                         add_tag = function(tag, content){
                           glued <- glue("<{tag}>{content}")
                           self$body <- c(self$body, glued)
                         },
                         initialize = function(name){
                           self$name <- name
                         },
                         save = function(path){
                           write(private$concat(self$head, self$style, self$body), 
                                 glue("{file.path(path, self$name)}.html")
                           )
                         },
                         view = function(){
                           html_print(HTML(private$concat(self$head, private$shared$style, self$body)))
                         },
                         print = function(){
                           cat(private$concat(self$head, private$shared$style, self$body), 
                               sep = "\n")
                         }
                       ), 
                       private = list(
                         concat = function(head, style, body){
                           c(head, style, "", body,"","")
                         }, 
                         shared = {
                           env <- new.env()
                           env$style <- '

Hey there!

You've reached the rtask teams!

We are glad to have you here.

Enjoying R6 already?

index$view()


# Initiate a new page
team <- WebSkeleton$new("team")
# Don't assign any style
team$add_tag("h2", "You again?")
team$add_tag("h3", "Time to meet the team!")
team$add_tag("p", "Vincent: [email protected] - https://twitter.com/VincentGuyader")
team$add_tag("p", "Diane: [email protected] - https://twitter.com/DianeBELDAME")
team$add_tag("p", "Colin: [email protected] - https://twitter.com/_ColinFay")
team$add_tag("p", "Sebastien: [email protected] - https://twitter.com/StatnMap")

team$view()

As you can see, I didn’t have to assign any style to get access to the CSS. And if I add another style to my team object:

team$add_style("p", list("font-weight" = "bold"))
team$view()


It will be shared to the index page.

index$view()

?

The CSS is now shared between all instances of the "WebSkeleton" class.

How does this work?

So, here’s a quick demo to show you that public elements are not shared between class instances, but that environments are.

plop <- R6Class("plop", 
                public = list(a = 1, 
                              change_a = function(val) { self$a <- val }, 
                              print_b = function() { private$shared$b }, 
                              change_b = function(val) { private$shared$b <- val} ), 
                private = list( shared = { e <- new.env(); e$b <- 2; e } )
)

plop_1 <- plop$new()
plop_1$a
[1] 1

plop_2 <-  plop$new()
plop_2$change_a(12)
plop_2$a
[1] 12

plop_1$a
[1] 1

plop_1$print_b()
[1] 12
plop_2$print_b()
[1] 12

plop_1$change_b(19)
plop_1$print_b()
[1] 19
plop_2$print_b()
[1] 19

Here, once plop_1<\code> is initialized, it contains the variable a with a specific value. If I initiate plop_2 and change_a<\code>, the only value that is changed is the one inside plop_2. But when it comes to b, as shared is an environment — and that environment is copied by reference, not by value —, we can share data across all instances.

Conceptually speaking, each instance of the a variable points to its own space in memory, while all the shared elements point to the same spot, hence contain the same value.

The post [How-to] Share content between several R6 instances appeared first on The R Task Force.

To leave a comment for the author, please follow the link and comment on their blog: The R Task Force.

R-bloggers.com offers daily e-mail updates about R news and tutorials on topics such as: Data science, Big Data, R jobs, visualization (ggplot2, Boxplots, maps, animation), programming (RStudio, Sweave, LaTeX, SQL, Eclipse, git, hadoop, Web Scraping) statistics (regression, PCA, time series, trading) and more...



If you got this far, why not subscribe for updates from the site? Choose your flavor: e-mail, twitter, RSS, or facebook...

Comments are closed.

Search R-bloggers

Sponsors

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)