๐ŸŽ“ A1Learner
๐Ÿ”
0 of 27 lessons completed

R S3 Group Generics

A group generic is one method that answers a whole group of operators. An Ops method is named Ops with the class name after a dot.

Add two wallets

Ops.wallet answers the plus call and builds a new wallet from the two balances. The total prints 15 and the result still carries the wallet class.

Ops.wallet <- function(e1, e2) {
  if (.Generic == "+") {
    structure(list(balance = e1$balance + e2$balance), class = "wallet")
  } else {
    stop("wallets only add")
  }
}
w1 <- structure(list(balance = 10), class = "wallet")
w2 <- structure(list(balance = 5), class = "wallet")
total <- w1 + w2
print(total$balance)
print(class(total))

Subtraction is refused

The minus call reaches the same method with a different .Generic value. The method stops, tryCatch catches the stop, and the message prints as a plain value.

Ops.wallet <- function(e1, e2) {
  if (.Generic == "+") {
    structure(list(balance = e1$balance + e2$balance), class = "wallet")
  } else {
    stop("wallets only add")
  }
}
w1 <- structure(list(balance = 10), class = "wallet")
w2 <- structure(list(balance = 5), class = "wallet")
result <- tryCatch(w1 - w2, error = function(e) {
  conditionMessage(e)
})
print(result)

Comparison reaches the same method

The equality operator belongs to the same group, so it lands in Ops.wallet too. The method refuses it with the same message.

Ops.wallet <- function(e1, e2) {
  if (.Generic == "+") {
    structure(list(balance = e1$balance + e2$balance), class = "wallet")
  } else {
    stop("wallets only add")
  }
}
w1 <- structure(list(balance = 10), class = "wallet")
w2 <- structure(list(balance = 5), class = "wallet")
result <- tryCatch(w1 == w2, error = function(e) {
  conditionMessage(e)
})
print(result)

Add three wallets in a chain

Each addition returns a wallet, so the second plus call uses Ops.wallet again. The three balances add to 35.

Ops.wallet <- function(e1, e2) {
  if (.Generic == "+") {
    structure(list(balance = e1$balance + e2$balance), class = "wallet")
  } else {
    stop("wallets only add")
  }
}
w1 <- structure(list(balance = 10), class = "wallet")
w2 <- structure(list(balance = 5), class = "wallet")
w3 <- structure(list(balance = 20), class = "wallet")
total <- w1 + w2 + w3
print(total$balance)

Example

Ops.wallet <- function(e1, e2) {
  if (.Generic == "+") {
    structure(list(balance = e1$balance + e2$balance), class = "wallet")
  } else {
    stop("wallets only add")
  }
}
purse <- structure(list(balance = 10), class = "wallet")
gift <- structure(list(balance = 5), class = "wallet")
topup <- structure(list(balance = 20), class = "wallet")
total <- purse + gift + topup
print(total$balance)
result <- tryCatch(total - gift, error = function(e) {
  conditionMessage(e)
})
print(result)

Key points

  • R calls the Ops method for the arithmetic, comparison and logical operators on the class.
  • .Generic holds the text name of the operator that was called.
  • The method reads .Generic and chooses what to do for that operator.
  • An operator the method does not allow can stop with its own message.
  • The answer to an allowed operator can carry the class again, so the result joins the next operation.

In this example, three wallets add into a total of 35 because each addition returns a wallet. The subtraction after it reaches the same Ops method and its refusal is caught and printed.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท rdrr.io R Snippets

Exercise

Inside Ops.wallet, how does the method know which operator was called?