16.ane. S3 Classes¶

Contents

  • The Bones Thought
  • Retentivity Management
  • Creating an S3 Class
    • Direct Forrard Approach
    • Local Surroundings Approach
  • Creating Methods
    • Straight Forwards Approach
    • Local Environment Arroyo
  • Inheritance

We examine how to create S3 classes. It is assumed that yous are familiar with the basic data types and scripting (Introduction to Programming).

16.ane.1. The Basic Idea¶

First, everything in R is treated like as an object. We take seen this with functions. Many of the objects that are created within an R session have attributes associated with them. One common attribute associated with an object is its class.

You can set up the form attribute using the course command. I affair to discover is that the class is a vector which allows an object to inherit from multiple classes, and information technology allows you to specify the guild of inheritance for complex classes. You can as well employ the form command to decide the classes associated with an object.

                                    >                  bubba                  <-                  c                  (                  1                  ,                  2                  ,                  3                  )                  >                  bubba                  [ane] one ii three                  >                  >                                    form                  (bubba)                  [1] "numeric"                  >                  >                                    class                  (bubba)                  <-                  append                  (                  grade                  (bubba),                  "Flamboyancy"                  )                  >                                    grade                  (bubba)                  [1] "numeric"     "Flamboyancy"                

Note

A new command, append, is used hither. The showtime argument is a vector, and the function adds the post-obit arguments to the cease of the vector.

I manner to ascertain a method for a class is to utilise the UseMethod command to define a hierarchy of functions that will react appropriately. The UseMethod command will tell R to look for a role whose prefix matches the electric current function, and it searches for a suffix in order from the vector of class names. In other words a set up of functions can be divers, and the function that is actually called will exist adamant past the course name of the showtime object in the list of arguments.

You get-go have to define a generic function to reserve the function proper noun. The UseMethod control is then used to tell the R organisation to search for a different role. The search is based on the proper noun of the role and the names of an object's classes. The name of the functions accept two parts separated past a "." where the prefix is the function name and the suffix is the name of a class.

That is a lot of verbiage to describe a relatively simple idea. A very basic case is given below:

                                    >                  bubba                  <-                  list                  (start=                  "one"                  ,                  second=                  "two"                  ,                  3rd=                  "third"                  )                  >                                    form                  (bubba)                  <-                  append                  (                  grade                  (bubba),                  "Flamboyancy"                  )                  >                  >                  bubba                  $kickoff                  [1] "ane"                  $second                  [1] "two"                  $third                  [1] "third"                  attr(,"class")                  [i] "list"        "Flamboyancy"                  >                  >                  GetFirst                  <-                  function                  (x)                  +                                    {                  +                                    UseMethod                  (                  "GetFirst"                  ,10)                  +                                    }                  >                  >                  GetFirst.Flamboyancy                  <-                  role                  (10)                  +                                    {                  +                                    return                  (x$offset)                  +                                    }                  >                  >                  GetFirst(bubba)                  [i] "1"                

16.1.ii. Retention Direction¶

The plethora of object oriented approaches leads to a natural question. Which one should you lot use? With respect to S3 and S4 classes, the S3 course is more flexible, and the S4 grade is a more structured approach. This is a prissy way of maxim that the S3 class arroyo is for unaware slobs and is a sloppy manner to shoot yourself in the foot, while the S4 course is for uptight pedants.

Our focus here is on S3 classes. Before we delve into the details of S3 classes we need to talk almost retentiveness environments. These tin can exist used to great issue in S3 classes to brand your codes totally incomprehensible. On the down side they aid give S3 classes their flexibility.

An surroundings can be thought of as a local scope. It has a prepare of variables associated with information technology. You can access those variables if you have the "ID'' associated with the surroundings. There are a number of commands y'all tin utilize to manipulate and obtain the pointers to your environments. Yous can also apply the assign and get commands to ready and get the values of variables within an environment.

The environment command can be used to get the pointer to the electric current environment.

                                    >                                    ls                  ()                  character(0)                  >                  e                  <-                  surround                  ()                  >                  e                  <surroundings: R_GlobalEnv>                  >                                    assign                  (                  "bubba"                  ,                  3                  ,e)                  >                                    ls                  ()                  [1] "bubba" "e"                  >                  bubba                  [ane] 3                  >                                    get                  (                  "bubba"                  ,e)                  [1] 3                

Environments can be created and embedded within other environments and tin exist structured to form a bureaucracy. There are a number of commands to assist you movement through different environments. You can find more details using the control help(environment), but we do not pursue more details because this is as much as nosotros need for our purposes of using S3 classes.

sixteen.1.iii. Creating an S3 Form¶

The bones ideas associated with S3 classes is discussed in the showtime section (The Bones Idea). We now expand on that thought and demonstrate how to define a function that will create and return an object of a given grade. The basic idea is that a list is created with the relevant members, the list'southward class is set, and a copy of the list is returned.

Here we examine two different ways to construct an S3 class. The starting time approach is more commonly used and is more than straightforward. Information technology makes utilize of bones list properties. The second approach makes use of the local surround inside a function to define the variables tracked by the class. The reward to the second approach is that it looks more like the object oriented approach that many are familiar with. The disadvantage is that it is more difficult to read the code, and it is more like working with pointers which is unlike from the way other objects work in R.

16.i.3.1. Straight Forward Approach¶

The kickoff approach is the more standard approach almost ofttimes seen with S3 classes. Information technology makes apply of the methods defined outside of the class which is described below, Creating Methods. It likewise keeps track of the data maintained by the course using the standard practices associated with lists.

The basic thought is that a function is defined which creates a list. The data entries tracked by the class are defined in the list. In the instance below the defaults are specified in the argument list with default values assigned. A new grade proper noun is appended to the list'due south classes, and the listing is returned.

                    NorthAmerican                    <-                    function                    (eatsBreakfast=                    True                    ,myFavorite=                    "cereal"                    )                    {                    me                    <-                    list                    (                    hasBreakfast                    =                    eatsBreakfast,                    favoriteBreakfast                    =                    myFavorite                    )                    ## Gear up the proper name for the form                    form                    (me)                    <-                    append                    (                    form                    (me),                    "NorthAmerican"                    )                    return                    (me)                    }                  

Once this definition is executed a new function is defined, chosen NorthAmerican. A new object of this grade can be created by calling the part.

                                        >                    bubba                    <-                    NorthAmerican()                    >                    bubba                    $hasBreakfast                    [1] TRUE                    $favoriteBreakfast                    [1] "cereal"                    attr(,"form")                    [ane] "list"          "NorthAmerican"                    >                    bubba$hasBreakfast                    [one] TRUE                    >                    >                    louise                    <-                    NorthAmerican(eatsBreakfast=                    True                    ,myFavorite=                    "fried eggs"                    )                    >                    louise                    $hasBreakfast                    [1] TRUE                    $favoriteBreakfast                    [1] "fried eggs"                    attr(,"class")                    [1] "list"          "NorthAmerican"                  

16.1.3.2. Local Environment Arroyo¶

Another approach tin be employed that makes use of the local surround within a function to admission the variables. When nosotros define methods with this arroyo later, Local Environment Approach, the results will expect more than like object oriented approaches seen in other languages.

The arroyo relies on the local scope created when a office is called. A new surroundings is created that tin can be identified using the environment command. The environment can be saved in the list created for the course, and the variables inside this scope can then be accessed using the identification of the environment.

In the example below this arroyo appears to crave more than overhead. When we examine how to add external methods to the class the advantage will be a little clearer.

                    NordAmericain                    <-                    office                    (eatsBreakfast=                    Truthful                    ,myFavorite=                    "cereal"                    )                    {                    ## Become the environs for this                    ## instance of the function.                    thisEnv                    <-                    environment                    ()                    hasBreakfast                    <-                    eatsBreakfast       favoriteBreakfast                    <-                    myFavorite                    ## Create the listing used to correspond an                    ## object for this class                    me                    <-                    list                    (                    ## Define the environment where this listing is defined so                    ## that I can refer to it later.                    thisEnv                    =                    thisEnv,                    ## The Methods for this grade ordinarily become hither just are discussed                    ## below. A simple placeholder is here to requite you a teaser....                    getEnv                    =                    role                    ()                    {                    return                    (                    get                    (                    "thisEnv"                    ,thisEnv))                    }                    )                    ## Define the value of the listing within the electric current environment.                    assign                    (                    'this'                    ,me,envir=thisEnv)                    ## Set the name for the form                    class                    (me)                    <-                    append                    (                    form                    (me),                    "NordAmericain"                    )                    return                    (me)                    }                  

Now that the class is defined, the environs used for a given object can be easily retrieved.

                                        >                    bubba                    <-                    NordAmericain()                    >                                        become                    (                    "hasBreakfast"                    ,bubba$getEnv())                    [ane] Truthful                    >                                        get                    (                    "favoriteBreakfast"                    ,bubba$getEnv())                    [1] "cereal"                  

Note that there is an unfortunate side outcome to this approach. Past keeping track of the environs, it is similar to using a pointer to the variables rather than the variables themselves. This means when you make a copy, you are making a copy of the pointer to the surroundings.

                                        >                    bubba                    <-                    NordAmericain(myFavorite=                    "oatmeal"                    )                    >                                        get                    (                    "favoriteBreakfast"                    ,bubba$getEnv())                    [one] "oatmeal"                    >                    louise                    <-                    bubba                    >                                        assign                    (                    "favoriteBreakfast"                    ,                    "toast"                    ,louise$getEnv())                    >                                        go                    (                    "favoriteBreakfast"                    ,louise$getEnv())                    [1] "toast"                    >                                        get                    (                    "favoriteBreakfast"                    ,bubba$getEnv())                    [1] "toast"                  

This result volition exist explored over again in the subsection beneath detailing how to create methods for an S3 form. If you wish to exist able to copy an object using this approach you lot demand to create a new method to return a proper copy.

16.1.4. Creating Methods¶

We at present explore how to create methods associated with a form. Over again we break information technology up into ii parts. The first approach is used for both approaches discussed in the previous section. If you brand apply of the local environment approach discussed above y'all will likely make use of both approaches discussed in this section. If you only make utilise of the more straight forwards approach you lot only need to exist enlightened of the outset approach discussed here.

sixteen.1.4.1. Straight Forwards Approach¶

The showtime approach is to define a part that exists outside of the class. The function is defined in a generic fashion, and so a function specific to a given form is defined. The R surround then decides which function to utilize based on the course names of an statement to the function, and the suffix used in the names of the associated functions.

One matter to keep in mind is that for assignment R makes copies of objects. The implication is that if yous alter a part of an object y'all demand to render an exact copy of the object. Otherwise your changes may exist lost.

In the examples below we define accessors for the variables in the class defined above. We presume that the grade NorthAmerican is divers in the same style as the outset example above, Straightforward Class. In the beginning example the goal is that we want to create a part that volition set the value of hasBreakfast for a given object. The name of the function volition exist setHasBreakfast.

The first step is to reserve the proper name of the office, and apply the UseMethod command to tell R to search for the correct office. If we pass an object whose grade includes the proper name NorthAmerican then the correct function to call should exist called setHasBreakfast.NorthAmerican. Note that nosotros will also create a office called setHasBreakfast.default. This function volition exist called if R cannot notice some other function of the correct grade.

                    setHasBreakfast                    <-                    function                    (elObjeto,                    newValue)                    {                    print                    (                    "Calling the base of operations setHasBreakfast function"                    )                    UseMethod                    (                    "setHasBreakfast"                    ,elObjeto)                    print                    (                    "Annotation this is not executed!"                    )                    }                    setHasBreakfast.default                    <-                    function                    (elObjeto,                    newValue)                    {                    print                    (                    "You screwed upwardly. I do not know how to handle this object."                    )                    return                    (elObjeto)                    }                    setHasBreakfast.NorthAmerican                    <-                    office                    (elObjeto,                    newValue)                    {                    impress                    (                    "In setHasBreakfast.NorthAmerican and setting the value"                    )                    elObjeto$hasBreakfast                    <-                    newValue                    return                    (elObjeto)                    }                  

The first thing to notation is that the office returns a copy of the object passed to it. R passes copies of objects to functions. If you change an object within a part it does not change the original object. You must pass back a copy of the updated object.

                                        >                    bubba                    <-                    NorthAmerican()                    >                    bubba$hasBreakfast                    [1] TRUE                    >                    bubba                    <-                    setHasBreakfast(bubba,                    FALSE                    )                    [1] "Calling the base setHasBreakfast function"                    [1] "In setHasBreakfast.NorthAmerican and setting the value"                    >                    bubba$hasBreakfast                    [1] Fake                    >                    bubba                    <-                    setHasBreakfast(bubba,                    "No type checking sucker!"                    )                    [i] "Calling the base setHasBreakfast office"                    [1] "In setHasBreakfast.NorthAmerican and setting the value"                    >                    bubba$hasBreakfast                    [1] "No blazon checking sucker!"                  

If the correct office cannot exist institute then the default version of the function is called.

                                        >                    someNumbers                    <-                    ane                    :                    4                    >                    someNumbers                    [1] 1 two 3 iv                    >                    someNumbers                    <-                    setHasBreakfast(someNumbers,                    "what?"                    )                    [one] "Calling the base setHasBreakfast role"                    [1] "You screwed upward. I practise not know how to handle this object."                    >                    someNumbers                    [1] ane two iii 4                  

It is a proficient practice to only utilize predefined accessors to become and set values held by an object. Every bit a matter of completeness we define methods to get the value of the hasBreakfast field.

                    getHasBreakfast                    <-                    function                    (elObjeto)                    {                    print                    (                    "Calling the base getHasBreakfast function"                    )                    UseMethod                    (                    "getHasBreakfast"                    ,elObjeto)                    impress                    (                    "Annotation this is not executed!"                    )                    }                    getHasBreakfast.default                    <-                    function                    (elObjeto)                    {                    print                    (                    "Yous screwed upwardly. I do not know how to handle this object."                    )                    return                    (                    NULL                    )                    }                    getHasBreakfast.NorthAmerican                    <-                    function                    (elObjeto)                    {                    print                    (                    "In getHasBreakfast.NorthAmerican and returning the value"                    )                    return                    (elObjeto$hasBreakfast)                    }                  

The functions to become the values are used in the same way.

                                        >                    bubba                    <-                    NorthAmerican()                    >                    bubba                    <-                    setHasBreakfast(bubba,                    "No type checking sucker!"                    )                    [1] "Calling the base setHasBreakfast function"                    [1] "In setHasBreakfast.NorthAmerican and setting the value"                    >                    result                    <-                    getHasBreakfast(bubba)                    [1] "Calling the base getHasBreakfast function"                    [one] "In getHasBreakfast.NorthAmerican and returning the value"                    >                    consequence                    [1] "No type checking sucker!"                  

xvi.1.four.2. Local Environs Approach¶

If the second method for defining an S3 form is used as seen above, Local Environment Class, then the approach for defining a method can include an boosted way to define a method. In this arroyo functions tin can be defined inside the list that defines the object.

                    NordAmericain                    <-                    function                    (eatsBreakfast=                    TRUE                    ,myFavorite=                    "cereal"                    )                    {                    ## Go the environment for this                    ## instance of the function.                    thisEnv                    <-                    environment                    ()                    hasBreakfast                    <-                    eatsBreakfast       favoriteBreakfast                    <-                    myFavorite                    ## Create the list used to represent an                    ## object for this class                    me                    <-                    list                    (                    ## Define the environment where this list is divers then                    ## that I tin can refer to it later on.                    thisEnv                    =                    thisEnv,                    ## Define the accessors for the data fields.                    getEnv                    =                    function                    ()                    {                    return                    (                    get                    (                    "thisEnv"                    ,thisEnv))                    },                    getHasBreakfast                    =                    function                    ()                    {                    render                    (                    become                    (                    "hasBreakfast"                    ,thisEnv))                    },                    setHasBreakfast                    =                    function                    (value)                    {                    return                    (                    assign                    (                    "hasBreakfast"                    ,value,thisEnv))                    },                    getFavoriteBreakfast                    =                    function                    ()                    {                    return                    (                    get                    (                    "favoriteBreakfast"                    ,thisEnv))                    },                    setFavoriteBreakfast                    =                    function                    (value)                    {                    return                    (                    assign                    (                    "favoriteBreakfast"                    ,value,thisEnv))                    }                    )                    ## Define the value of the listing inside the current environment.                    assign                    (                    'this'                    ,me,envir=thisEnv)                    ## Prepare the name for the class                    class                    (me)                    <-                    suspend                    (                    form                    (me),                    "NordAmericain"                    )                    return                    (me)                    }                  

With this definition the methods tin can be chosen in a more directly fashion.

                                        >                    bubba                    <-                    NordAmericain(myFavorite=                    "oatmeal"                    )                    >                    bubba$getFavoriteBreakfast()                    [ane] "oatmeal"                    >                    bubba$setFavoriteBreakfast(                    "apparently toast"                    )                    >                    bubba$getFavoriteBreakfast()                    [one] "obviously toast"                  

As noted above, Local Surround Class, this arroyo can be problematic when making a re-create of an object. If you need to brand copies of your objects a function must be defined to explicitly make a re-create.

                    makeCopy                    <-                    function                    (elObjeto)                    {                    print                    (                    "Calling the base makeCopy function"                    )                    UseMethod                    (                    "makeCopy"                    ,elObjeto)                    print                    (                    "Annotation this is not executed!"                    )                    }                    makeCopy.default                    <-                    function                    (elObjeto)                    {                    print                    (                    "You screwed upward. I do not know how to handle this object."                    )                    render                    (elObjeto)                    }                    makeCopy.NordAmericain                    <-                    office                    (elObjeto)                    {                    print                    (                    "In makeCopy.NordAmericain and making a copy"                    )                    newObject                    <-                    NordAmericain(                    eatsBreakfast=elObjeto$getHasBreakfast(),                    myFavorite=elObjeto$getFavoriteBreakfast())                    return                    (newObject)                    }                  

With this definition we can at present make a proper copy of the object and get the expected results.

                                        >                    bubba                    <-                    NordAmericain(eatsBreakfast=                    FALSE                    ,myFavorite=                    "oatmeal"                    )                    >                    louise                    <-                    makeCopy(bubba)                    [i] "Calling the base makeCopy function"                    [one] "In makeCopy.NordAmericain and making a copy"                    >                    louise$getFavoriteBreakfast()                    [ane] "oatmeal"                    >                    louise$setFavoriteBreakfast(                    "eggs"                    )                    >                    louise$getFavoriteBreakfast()                    [ane] "eggs"                    >                    bubba$getFavoriteBreakfast()                    [1] "oatmeal"                  

16.one.five. Inheritance¶

Inheritance is part of what makes it worthwhile to get to the endeavor of making up a proper class. The basic idea is that another class can be constructed that makes use of all the data and methods of a base class and builds on them by adding additional data and methods.

The basic thought is that an object'due south form is a vector that contains an ordered list of classes that an object is a fellow member of. When a new object is created it can add its class proper name to that listing. The methods associated with the course tin employ the NextMethod command to search for the function associated with the next class in the list.

In the examples beneath we build on the example of the NorthAmerican course defined above. The examples below presume that the NorthAmerican class given above is defined, and the class hierarchy is shown in Figure 1..

Diagram of the NorthAmerican derived classes.

Figure ane.

Diagram of the NorthAmerican derived classes.

A file with the full script is available at s3Inheritance.R . Nosotros exercise not provide the full code prepare hither in club to keep the give-and-take somewhat under command. Our first stride is to define the new classes.

                  Mexican                  <-                  role                  (eatsBreakfast=                  TRUE                  ,myFavorite=                  "los huevos"                  )                  {                  me                  <-                  NorthAmerican(eatsBreakfast,myFavorite)                  ## Add together the proper name for the class                  class                  (me)                  <-                  append                  (                  class                  (me),                  "Mexican"                  )                  return                  (me)                  }                  USAsian                  <-                  function                  (eatsBreakfast=                  TRUE                  ,myFavorite=                  "pork belly"                  )                  {                  me                  <-                  NorthAmerican(eatsBreakfast,myFavorite)                  ## Add together the name for the class                  class                  (me)                  <-                  append                  (                  class                  (me),                  "USAsian"                  )                  return                  (me)                  }                  Canadian                  <-                  function                  (eatsBreakfast=                  Truthful                  ,myFavorite=                  "back bacon"                  )                  {                  me                  <-                  NorthAmerican(eatsBreakfast,myFavorite)                  ## Add the proper noun for the class                  class                  (me)                  <-                  suspend                  (                  grade                  (me),                  "Canadian"                  )                  return                  (me)                  }                  Anglophone                  <-                  function                  (eatsBreakfast=                  Truthful                  ,myFavorite=                  "pancakes"                  )                  {                  me                  <-                  Canadian(eatsBreakfast,myFavorite)                  ## Add the name for the class                  class                  (me)                  <-                  append                  (                  class                  (me),                  "Anglophone"                  )                  return                  (me)                  }                  Francophone                  <-                  function                  (eatsBreakfast=                  TRUE                  ,myFavorite=                  "crepes"                  )                  {                  me                  <-                  Canadian(eatsBreakfast,myFavorite)                  ## Add the name for the class                  grade                  (me)                  <-                  append                  (                  course                  (me),                  "Francophone"                  )                  return                  (me)                  }                

With these definitions we can define an object. In this case we create an object from the Francophone class.

                                    >                  francois                  <-                  Francophone()                  >                  francois                  $hasBreakfast                  [1] Truthful                  $favoriteBreakfast                  [1] "crepes"                  attr(,"class")                  [1] "list"          "NorthAmerican" "Canadian"      "Francophone"                

The thing to notice is that the form vector demonstrates the class inheritance structure. We tin now define a method, makeBreakfast which will brand employ of the form construction.

                  makeBreakfast                  <-                  role                  (theObject)                  {                  impress                  (                  "Calling the base makeBreakfast function"                  )                  UseMethod                  (                  "makeBreakfast"                  ,theObject)                  }                  makeBreakfast.default                  <-                  office                  (theObject)                  {                  print                  (                  noquote                  (                  paste                  (                  "Well, this is bad-mannered. Simply make"                  ,                  getFavoriteBreakfast(theObject))))                  render                  (theObject)                  }                  makeBreakfast.Mexican                  <-                  function                  (theObject)                  {                  print                  (                  noquote                  (                  paste                  (                  "Estoy cocinando"                  ,                  getFavoriteBreakfast(theObject))))                  NextMethod                  (                  "makeBreakfast"                  ,theObject)                  return                  (theObject)                  }                  makeBreakfast.USAsian                  <-                  office                  (theObject)                  {                  print                  (                  noquote                  (                  paste                  (                  "Get out me lonely I am making"                  ,                  getFavoriteBreakfast(theObject))))                  NextMethod                  (                  "makeBreakfast"                  ,theObject)                  return                  (theObject)                  }                  makeBreakfast.Canadian                  <-                  function                  (theObject)                  {                  print                  (                  noquote                  (                  paste                  (                  "Expert morning time, how would yous like"                  ,                  getFavoriteBreakfast(theObject))))                  NextMethod                  (                  "makeBreakfast"                  ,theObject)                  render                  (theObject)                  }                  makeBreakfast.Anglophone                  <-                  function                  (theObject)                  {                  print                  (                  noquote                  (                  paste                  (                  "I promise it is okay that I am making"                  ,                  getFavoriteBreakfast(theObject))))                  NextMethod                  (                  "makeBreakfast"                  ,theObject)                  return                  (theObject)                  }                  makeBreakfast.Francophone                  <-                  function                  (theObject)                  {                  print                  (                  noquote                  (                  paste                  (                  "Je cuisine"                  ,                  getFavoriteBreakfast(theObject))))                  NextMethod                  (                  "makeBreakfast"                  ,theObject)                  render                  (theObject)                  }                

Note that the functions call the NextMethod function to telephone call the next function in the listing of classes.

                                    >                  francois                  <-                  makeBreakfast(francois)                  [1] "Calling the base makeBreakfast function"                  [1] Good morning, how would you similar crepes                  [1] Je cuisine crepes                  [ane] Well, this is bad-mannered. Merely brand crepes                

It is important to note the order that the methods are called. They are called from left to right in the listing of classes. Another thing to note is that when the methods run out of class names the default function is chosen.