Menu
Coddy logo textTech

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] 150

Every 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 5

The 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] 100

The 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] 10
challenge icon

Challenge

Easy

Complete 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 = "")
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online R compiler