Skip to main content

Solving the OOP's "Chaos" for R

· 5 min read
Hategekimana Fabrice
IT trainer, Programming language designer

Introduction: The Object-Oriented Dilemma in R

For R package and application developers, the Object-Oriented Programming (OOP) landscape is, at best, a minefield. Between S3, which is informal and flexible but lacks validation; S4, which is formal but heavy; and R6, which introduces reference semantics but deviates from R's functional style, choosing an OOP system is often a painful compromise. This fragmentation slows down development, complicates maintenance, and introduces security flaws into the code.

TypR, a new typed version of the R language, offers a radical solution: the complete unification of the OOP system, reinforced by powerful static typing. Designed specifically for package developers, TypR aims to restore simplicity, speed, and security to the heart of the R ecosystem.

Unified OOP: Ending the OOP System War

TypR directly addresses the confusion by replacing existing systems (currently S3, and soon R6) with a single, coherent approach.

R OOP SystemMain CharacteristicDeveloper ProblemTypR Solution
S3Single dispatch, informalLack of validation, runtime errorsReplaced by a typed and validated system
S4Multiple dispatch, formalHeavy, complex syntaxReplaced by a simple, uniform syntax
R6Reference semanticsDeviates from R idiom, fragmentationReplaced by a unified approach with UFCS

TypR offer an abstraction that allows for increased development speed and simplified maintenance, as there is only one way to design robust objects.

Simplified OOP: Development Speed

In TypR, the developer focuses on defining types and functions, letting the language manage the underlying OOP implementation.

If I want to create a Person class, I just have to define a type and it's constructor:

type Person <- list {
name: char,
age: int
};

let new_person <- fn(name: char, age: int): Person {
list(name = name, age = age)
};

new_person("Alice", 32);
# $name
# Alice
# $age
# 32
# attr(,"class")
# [1] "Person" "any"

If you want to create a method for this type, you just have to create a function with this type as the first parameter:

let print <- fn(p: Person): Empty {
cat("Person<", p$name, ",", p$age, ">", sep="")
};

let person <- new_person("Alice", 32);

print(person)
# Person<Alice,32>

Underneath this implementation, TypR automatically created a Person class and implemented the generic function print.

This is the general shift from classic OOP:

Classic OOPTypR's OOP
ClassesTypes
MethodesFunctions
inheritancesubtyping

I choose to use types instead of classes because they are more versatil. While we can have classes and interfaces in modern OOP, TypR offer product types, union types, intersection types, interface types and exponential types.

Inheritance is no more a manual task. TypR's type inference is smart enougth to know when a type is a subtype of another. If it's the case, the subtype directly inherit the methods of its parent.

For instance the record type list { age: int } is a super type of Person (since Person is just an alias for the type list {name: char, age: int}). It's counter intuitive at firts, but in type theory, if all the fields of a list are present in a second list, the second list is a subtype of the first one. To help you understand this concept, you can read list { age: int } as "the set of list who have at least the field 'age: int'" and this is the case for Person.

With that in mind, if I create a method is_minor() for list { age: int }, Person will automatically inherit this method by the subtyping principle.

let is_minor <- fn(a: list { age: int }): bool {
a$age < 18
};

let person <- new_person("Bob", "15");

person |> is_minor()
# TRUE

Also types brings other cool powers:

  • Development Speed: Typing opens the door to advanced development tools. Auto-completion becomes more precise, and refactoring tools can operate with complete safety, significantly accelerating the iteration cycle.
  • Clarity and Readability: Explicit type definition acts as built-in documentation, making the code easier for collaborators to read and understand.

UFCS: Function Chaining Reimagined

TypR integrates the Uniform Function Call Syntax (UFCS), a powerful feature popularized by languages like Nim. UFCS is the cornerstone of OOP unification in TypR, as it reconciles R's functional style with object-oriented syntax.

Traditionally, in R, we write:

f(x, y)

Or, with the pipe operator:

x |> f(y)

With TypR's UFCS, these two syntaxes become equivalent to the object-oriented syntax:

x.f(y)

UFCS allows the developer to use dot notation (x.f()) to call any function whose first argument is x, without that function having to be formally a "method" of the object x.

Taking our previous example, we can use a dot shaped function call for our person:

let person <- new_person("Bob", "15");

# regular function call
is_minor(person)

# pipe function call
person |> is_minor()

# dot function call
person.is_minor()

Benefits for R Developers:

  • Natural Piping: Function chaining becomes free and unambiguous. The dot operato can be used interchangeably with the pipe operator.
  • Method Discovery: IDEs can list all applicable functions for an object simply by typing object. (dot) or object |> (pipe), improving the ergonomics of writing code.
  • Uniformity: Whether you call a generic function or a type-specific method, the syntax remains the same, eliminating confusion between different calling styles.

Conclusion: The Future of R is Typed

TypR is not just a new version of R; it is a re-foundation of how robust packages and applications are built. By targeting the major pain points of R's fragmented OOP and adding the security of static typing and the fluidity of UFCS, TypR offers developers:

BenefitImpact
SimplicityA single, unified OOP system replaces the complexity of S3/S4/R6.
SpeedStatic typing for safe refactoring and precise auto-completion.
SecurityCompile-time error detection, reducing production bugs.

If you are an R package developer tired of compromising between flexibility and robustness, TypR is the revolution you have been waiting for. It is time to switch to a simpler, faster, and safer R. What do you think?