Closures as Objects
Part of the Object Oriented Programming section of Coddy's R journey. Lesson 17 of 57.
You met closures in Logic & Flow: a function made inside another function keeps the variables around it. A function factory that returns a list of such functions builds an object. Its local variables are the object's data, and the functions are its methods:
make_account <- function(balance = 0) {
deposit <- function(x) balance <<- balance + x
get_balance <- function() balance
list(deposit = deposit, get_balance = get_balance)
}
acc <- make_account(100)
acc$deposit(50)
print(acc$get_balance())Output:
[1] 150Every call of the factory runs its body again, so every object gets its own variables. Changing one account does not touch the other:
make_account <- function(balance = 0) {
list(deposit = function(x) balance <<- balance + x,
get_balance = function() balance)
}
ada <- make_account(100)
bo <- make_account(5)
ada$deposit(1)
cat(ada$get_balance(), bo$get_balance())Output:
101 5The data is private. balance is not an element of the list, so acc$balance is NULL and assigning to it only adds an unrelated element. The functions in the list are the object's whole public interface:
make_account <- function(balance = 0) {
list(get_balance = function() balance)
}
acc <- make_account(100)
print(acc$balance)
acc$balance <- 1e6
print(acc$get_balance())Output:
NULL
[1] 100The methods can call each other when they are defined as local functions before the list is built. Here add_many() reuses add():
make_tally <- function() {
total <- 0
add <- function(x) total <<- total + x
add_many <- function(xs) for (x in xs) add(x)
get <- function() total
list(add = add, add_many = add_many, get = get)
}
t <- make_tally()
t$add(1)
t$add_many(c(2, 3, 4))
print(t$get())Output:
[1] 10Challenge
EasyComplete the factory make_counter(start = 0) in Counter.R. It keeps the current value private and returns a list with four functions: increment() and decrement() change the value by 1, reset() sets it back to start, and value() returns it.
The supplied code reads the start value, then commands +, - or reset, and prints the value after each. At the end it prints the names in the returned list, which must be only the four functions.
Your code goes in Counter.R. main.R holds the supplied input/output code and cannot be edited.
Try it yourself
source("Counter.R")
# Supplied input/output code: keep it as it is
input <- suppressWarnings(readLines(file("stdin")))
counter <- make_counter(as.numeric(input[1]))
for (cmd in input[-1]) {
if (cmd == "+") counter$increment()
if (cmd == "-") counter$decrement()
if (cmd == "reset") counter$reset()
cat(cmd, " -> ", counter$value(), "\n", sep = "")
}
cat("fields visible: ", paste(sort(names(counter)), collapse = ", "), "\n", sep = "")
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1S3 Basics
Working With FilesLists With a ClassConstructor FunctionsPrint and Format MethodsValidators and HelpersRecap - Temperatures4Encapsulation in R
Closures as ObjectsEnvironments as ObjectsAccessor FunctionsGuarding StateRecap - Parking Meter2S3 Generics and Methods
Generics and UseMethodDefault MethodsMethods for Base GenericsNextMethod BasicsRecap - Shape Areas8Reference Classes
Defining Reference ClassesMethods and Field UpdatesCopy SemanticsInheritance and callSuperRecap - Task Queue11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Online R compiler