14 R6

14.1 Prerequisites

To solve the exercises in this chapter we will have to to create R6 objects, which are implemented in the R6 package.

14.2 Classes and methods

  1. Q: Create a bank account R6 class that stores a balance and allows you to deposit and withdraw money. Create a subclass that throws an error if you attempt to go into overdraft. Create another subclass that allows you to go into overdraft, but charges you a fee.

    A: Let’s start with a basic bank account, similar to the Accumulator class in the text book.

    To test this class we create one instance and leave it with a negative balance.

    Now, we create the first subclass that prevents us from going into overdraft and throws an error in case we attempt to withdraw more than our current balance.

    This time our test should throw an error.

    Finally, we create a class that charges a constant fee of 1 for each withdrawal which leaves the account with a negative balance.

    Let’s take a look at the implemented functionality. We expect a final balance of -12, because we pay the fee twice.

  2. Q: Create an R6 class that represents a shuffled deck of cards. You should be able to draw cards from the deck with $draw(n), and return all cards to the deck and reshuffle with $reshuffle(). Use the following code to make a vector of cards.

    A:

    To test this class we create a deck (initialise an instance), draw all the cards, then reshuffle, checking we get different cards each time.

  3. Q: Why can’t you model a bank account or a deck of cards with an S3 class?

    A: Because S3 classes obey R’s usual semantics of copy-on-modify: every time you deposties from the bank account or drew a card, you’d get a new copy of the object.

    It is possible to combine S3 classes with an environment (which is how R6 works), but it is ill-advised to create an object that looks like a regular R object but has reference semantics.

  4. Q: Create an R6 class that allows you to get and set the current time zone. You can access the current time zone with Sys.timezone() and set it with Sys.setenv(TZ = "newtimezone"). When setting the time zone, make sure the new time zone is in the list provided by OlsonNames().

    A: To create an R6 class that allows us to get and set the time zone, we provide the respective functions as public methods to the R6 class.

    (When setting, we return the old value invisibly because this make it easy to restore the previous value).

    Now, let us create one instance of this class and test, if we can can set and get the time zone as intended.

  5. Q: Create an R6 class that manages the current working directory. It should have $get() and $set() methods.

    A: Take a look at the following implementation, which is quite minimalistic:

  6. Q: Why can’t you model the time zone or current working directory with an S3 class?

    A: Because S3 classes are not suitable for modelling state that changes over time. S3 methods should (almost) always return the same result when called with the same inputs.

  7. Q: What base type are R6 objects built on top of? What attributes do they have?

    A: R6 objects are built on top of environments. They have a class attribute, which is a character vector containing the class name, the name of any super classes (if existent) and the string "R6" as the last element.

14.3 Controlling access

  1. Q: Create a bank account class that prevents you from directly setting the account balance, but you can still withdraw from and deposit to. Throw an error if you attempt to go into overdraft.

    A: To fulfil this requirement, we make balance a private field. The user has to use the $deposit() and $withdraw() which have access to the balance field.

    To test our new class, we create an instance and try to go into overdraft.

  2. Q: Create a class with a write-only $password field. It should have $check_password(password) method that returns TRUE or FALSE, but there should be no way to view the complete password.

    A: To protect the password from changes and direct access, the password will be a private field. Further, our Password will get its own print method which hides the password.

    Let’s create one instance of our new class and confirm that the password is neither accessible nor visible, but still check-able.

  3. Q: Extend the Rando class with another active binding that allows you to access the previous random value. Ensure that active binding is the only way to access the value.

    A: To access the previous random value from an instance, we add a private $last_random field to our class, and we modify $random() to write to this field, whenever it is called. To access the last_random field we provide $previous().

    Now, we initiate a new Rando object and see, if it behaves as expected.

  4. Q: Can subclasses access private fields/methods from their parent? Perform an experiment to find out.

    A: To find out if private fields/classes can be accessed from subclasses, we first create a class A with a private field foo and a private method bar(). Afterwards, an instance of a subclass B is created and calls the foobar() methods, which tries to access the foo field and the bar() method from its superclass A.

    We conclude, that subclasses can access private methods from their superclasses, but not private fields.

14.4 Reference semantics

  1. Q: Create a class that allows you to write a line to a specified file. You should open a connection to the file in $initialize(), append a line using cat() in $append_line(), and close the connection in $finalize().

    A: Our FileWriter class will create a connection to a file at initialization. Therefore, we open a connection to a user specified file during the initialisation. Note that we need to set open = "a" in file() to open connection for appending text. Otherwise cat would only work when applied to files, but not with connections as explicitly asked for in the exercise. Further, we add the append_line() method and a close() statement as finalizer.

    Let’s see, if new instances of our class work as expected.