Object-Oriented Programming in R (Part 1): An Introduction

Estimated time:
time
min

Object-oriented programming (OOP) is a popular and widely embraced programming paradigm in software development. The concept of object-oriented programming in R has been previously featured in one of our blog posts, <a href="https://appsilon.com/oop-in-r-with-r6/" target="_blank" rel="noopener">specifically within the context of R6 classes</a>.

In this blog post, we will dive deeper into the world of object-oriented programming, understand why it's a valuable approach worthy of adoption, and its implementation in R.
<h3>Table of Contents</h3><ul>  <li><a href="#motivation"><strong>What Was the Motivation Behind Introducing OOP in R?</strong></a></li>  <li><strong><a href="#developers-need-oop">Why Do Developers Need OOP?</a></strong></li>  <li><strong><a href="#what-is-oop">What is OOP?</a></strong></li>  <li><strong><a href="#oop-systems">OOP Systems in R</a></strong></li>  <li><strong><a href="#conclusion">Conclusion</a></strong></li></ul>

<hr />

<h2 id="motivation">What Was the Motivation Behind Introducing OOP in R?</h2>
The <a href="https://www.r-project.org/about.html" target="_blank" rel="noopener noreferrer">R Foundation describes R</a> as a language and environment for statistical computing and graphics. It originated from the <a href="https://en.wikipedia.org/wiki/S_(programming_language)" target="_blank" rel="noopener">S language</a> and environment, which was developed at Bell Laboratories.

The days of the S language is where we will start our tour on the history of OOP in R. S allowed users to use different kinds of statistical models. Even though statistical models can be different, they share a common set of operations, such as printing, making predictions, plotting, or updating the model.

<a href="https://projecteuclid.org/download/pdfview_1/euclid.ss/1408368569" target="_blank" rel="noopener noreferrer">Uniform functions were introduced</a> to make it easier for users to interact with those models. Good examples of such uniform functions are <code>print()</code>, <code>predict()</code>, <code>plot()</code>, <code>update()</code>. These functions can be invoked on any model, irrespective of whether it's a linear regression model or a time series model like ARIMA (Autoregressive Integrated Moving Average) working under the hood.
<blockquote>Interested in elevating your coding with Functional Programming in R? <a href="https://appsilon.com/functional-programming-in-r-part-1/" target="_blank" rel="noopener">Check out our introductory article; 'Unlocking the Power of Functional Programming in R</a>.</blockquote>
<h3 id="developers-need-oop">Why Do Developers Need OOP?</h3>
A uniform interface simplifies interactions for users of code that employs OOP principles, but what drives our choice to utilize OOP in our own code?

Let’s go back to the example of having different models and the uniform print function. Without using OOP, we might implement this function as:
<pre><code class="language-r">
print &lt;- function(x) {
 if (inherits(x, "lm")) {
   # print linear model
 } else if (inherits(x, "Arima")) {
   # print arima model
 }  
}
</code></pre>
While this might not look that bad, imagine how long that function would be if it supported printing every available model in R.

Another issue with this approach is that only the author of the function can add new types there. This reduces flexibility as developers who would want print to support their own classes, would need to reach out to the author of the print function.

Object-oriented programming allows us to have a separate implementation of the print function for each of our classes. Some of you might recognize that this example is similar to how it would look written in the S3 OOP; we will be diving deeper into S3 in subsequent posts.
<pre><code class="language-r">
print &lt;- function(x, ...) {
 # Generic function
}
<br>print.lm &lt;- function(x, ...) {
 # print linear model
}
<br>print.Arima &lt;- function(x, ...) {
 # print arima model
}
</code></pre>
&nbsp;

Much better! Our code is now:
<ol>  <li><strong>More modular</strong> - instead of one big function, we have multiple smaller functions, which improves readability and can make testing easier.</li>  <li><strong>Flexible</strong> - Potentially, other users can now add their own print functions without having to modify any existing ones.</li></ol>
<em>This section was inspired by Hadley Wickham's talk: <a href="https://youtu.be/P3FxCvSueag?si=ahHdAcoNCSsRav3A" target="_blank" rel="noopener noreferrer">An Introduction to R7</a>.</em>
<h2 id="what-is-oop">What is OOP?</h2>
Now, we have an idea of why we might want to use object-oriented programming, but have not yet defined what it is:

Object-oriented programming is a programming paradigm where we identify the following principles: <strong>Encapsulation</strong>, <strong>Polymorphism</strong>, <strong>Abstraction</strong>, and <strong>Inheritance</strong>.

In the article, we already had a chance to see encapsulation, polymorphism, and abstraction in action:
<ul>  <li><strong>Polymorphism</strong> allows us to perform the same action in different ways (call the print function but call it on different models).</li>  <li><strong>Encapsulation</strong> allows us to not worry about the internal details of the object when interacting with the object (e.g. how coefficients are stored in our linear model).</li>  <li><strong>Abstraction</strong> allows us to not worry about the internal implementation details of the object (for example, what method is used for fitting the linear regression).</li></ul>
We will explore inheritance in more detail when diving into specific OOP systems in R, but for completeness:
<ul>  <li><strong>Inheritance</strong> - classes can reuse code from other classes by designing relationships (hierarchy) between them. For example, in R, the <code>glm</code> class inherits from the <code>lm</code> class</li></ul>
Additionally, there are a couple of terms that are often used when talking about OOP (We already used some of them!)
<ul>  <li><strong>Classes</strong> - user-defined data types that serve as blueprints for creating objects; they define what fields or data an instance of the class contains (for example, an instance of the <code>lm</code> class has a <code>coefficients</code> field which contains a named vector of coefficients)</li>  <li><strong>Objects</strong> - instances of individual classes; for example, each linear regression model is a linear regression model, but they can differ from each other (for example, they can be trained on different data)</li>  <li><strong>Methods</strong> - a function associated with a given class; they describe what an object can do. For example, you can make predictions using a linear regression model.</li></ul>
<h2 id="oop-systems">OOP Systems in R</h2>
All right, so how do we do OOP in R? Turns out R provides different ways of doing OOP:
<ol>  <li>S3</li>  <li>S4</li>  <li>Reference Classes (referred to as RC or sometimes R5)</li>  <li><a href="https://appsilon.com/oop-in-r-with-r6/" target="_blank" rel="noopener">R6</a></li></ol>
<blockquote>Interested in experiencing R6 Classes in action while designing a video game in R Shiny? Check out our article, <a href="https://appsilon.com/is-it-possible-to-build-a-video-game-in-r-shiny/" target="_blank" rel="noopener">How to Build a Video Game in R Shiny with CSS, JavaScript, and R6 Classes.</a></blockquote>
On top of that, there is a new OOP being developed called <a href="https://rconsortium.github.io/S7/index.html" target="_blank" rel="noopener noreferrer">S7 (previously also called R7)</a>, and there are also other packages R packages providing ways of doing OOP in R including:
<ol>  <li><a href="https://adv-r.hadley.nz/oo.html" target="_blank" rel="noopener noreferrer">proto</a></li>  <li><a href="https://cran.r-project.org/web/packages/R.oo/index.html" target="_blank" rel="noopener noreferrer">R.oo</a></li></ol>
Some packages also defined their own OOP systems; for example, <a href="https://github.com/mlverse/torch/pull/38" target="_blank" rel="noopener noreferrer">torch defined its own OOP system called R7</a> (not to be confused with R7 developed by the R Consortium, which is now called S7).

<em>Each of those has its own advantages and disadvantages that we will be exploring in subsequent articles, so stay tuned.</em>
<h2 id="conclusion">Conclusion</h2>
The first appearance of OOP in R comes from the S language. Object-oriented programming was used in S to provide a common set of functions for interacting with statistical models. OOP makes it easy to provide end users with a uniform interface to a family of different classes (e.g. different statistical models).

OOP provides developers with flexibility and allows their code to be more modular. There are multiple ways of doing OOP in R, and more are being developed. We'll dive deeper into object-oriented programming in R; stay tuned for our next article in this series.

Have questions about Object-Oriented Programming (OOP) in R or need support with your enterprise R/Shiny project? <a href="https://appsilon.com/#contact" target="_blank" rel="noopener">Feel free to reach out to us for assistance!</a>

Contact us!
Damian's Avatar
Damian Rodziewicz
Head of Sales
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
object-oriented programming