retList
can be used as a shortcut to:
constructor <- function(...) {
...
out <- list(...)
class(out) <- "constructor"
out
}
vs.
constructor <- function(...) {
...
retList("constructor")
}
It will simply find all objects in the environment in which it is called and returns those in a list. This is one way to think about and implement features associated with object orientation in R.
Every value bound to a symbol will be public, objects with a leading dot in the name will be private. This can be changed by stating which names to make public explicitely.
Here is a simple class definition:
library("aoos")
Person <- function(name) {
print <- function(x, ...) {
cat(paste0("Hello, my name is ", .self$name, ".\n"))
}
retList(c("Person", "Print"))
}
retList
is used as a generic constructor function. It allways returns a list containing all visible values from the environment where it is called. There is a print method print.Print
in the package which will look for a member function called print for printing. Thus the print
method in the class Person will be used as S3 printing method.
ann <- Person("Ann")
ann
## Hello, my name is Ann.
Inside the class definition - i.e. the body of the constructor - you can use .self
but you do not have to; It just might be more explicit. You can also use the super-assignment operator <<-
. Note that .self
is not a reference to the instance itself, it is only referencing to the environment in which the methods will live in.
You should keep in mind that a public field is a value in a list and not a reference to the value in .self
. Thus, if your object should have public fields you have to define get/set methods. In this example you can think of name
as an attribute since changing it in an instance won’t change the bahavior of greet
. Here is something you can do instead:
Person <- function(.name) {
print <- function(x, ...) {
cat(paste0("Hello, my name is ", .self$.name, ".\n"))
}
name <- function(x) {
if (!missing(x)) .name <<- x
.name
}
retList(c("Person", "Print"))
}
p <- Person("Ann")
p
## Hello, my name is Ann.
p$name()
## [1] "Ann"
p$name("Paul")
## [1] "Paul"
p
## Hello, my name is Paul.
Although I don’t understand why ‘Ann’ can change to be ‘Paul’, this is one way of adding something like reference semantics.
An object can inherit from another object. The constructer then extends the instance of the super class/object. For that you can supply a call to the super classes constructor function, where you also have to supply arguments for initialization.
Person <- function(name) {
print <- function(x, ...) {
cat(paste0("Hello, my name is ", .self$name, ".\n"))
}
retList(c("Person", "Print"))
}
Employee <- function(id, ...) {
print <- function(x, ...) {
cat("Name: ", name, "\nID: ", id)
}
retList("Employee", super = Person(...))
}
kalle <- Employee("1", "Kalle")
str(kalle)
## List of 3
## $ id : chr "1"
## $ print:function (x, ...)
## ..- attr(*, "srcref")=Class 'srcref' atomic [1:8] 13 12 15 3 12 3 13 15
## .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment: 0x1f5424b0>
## $ name : chr "Kalle"
## - attr(*, ".self")=<environment: 0x1e6bb6e0>
## - attr(*, "class")= chr [1:4] "Employee" "Person" "Print" "list"
kalle
## Name: Kalle
## ID: 1