25

Up-front: I am aware that R is a functional language, so please don't bite ;-)

I've had great experiences with using an OOP approach for a lot of my programs. Now, I'm wondering if there's a way to make a distinction between public and private methods when using S4 Reference Classes in R?

Example

Class Definitions

setRefClass("B",
    field=list(
        b.1="numeric",
        b.2="logical"
    ),
    methods=list(
        thisIsPublic=function(...) {
            thisIsPublic_ref(.self=.self, ...)
        },
        thisIsPrivate=function(...) {
            thisIsPrivate_ref(.self=.self, ...)
        }
    )
)

setRefClass("A",
    field=list(
        a.1="B"
    )
)

NOTE

I usually do not place the actual method definition within the class def but separate it to a S4 method (i.e. thisIsPublic_ref) for the following reasons:

  1. That way the class def stays clearly arranged and is easier to read in cases when the individual method defs grow quite large.
  2. It allows you to switch to a functional execution of methods at any time. Be x an instance of a certain class, you are able to call foo_ref(.self=x) instead of x$foo().
  3. It allows you to byte-compile the methods via compiler::cmpfun() which I think is not possible if you have "plain" Reference Class methods.

It sure does not really make sense to make it that complicated for this specific example, but I thought I'd nevertheless illustrate that approach.

Method Definitions

setGeneric(
    name="thisIsPublic_ref",
    signature=c(".self"),
    def=function(
        .self,
        ...
    ) {
    standardGeneric("thisIsPublic_ref")    
    }
)
setGeneric(
    name="thisIsPrivate_ref",
    signature=c(".self"),
    def=function(
        .self,
        ...
    ) {
    standardGeneric("thisIsPrivate_ref")    
    }
)

require(compiler)

setMethod(
    f="thisIsPublic_ref",
    signature=signature(.self="B"),
    definition=cmpfun(function(  
        .self,
        ...
    ){
    .self$b.1 * 1000
    })
)
setMethod(
    f="thisIsPrivate_ref",
    signature=signature(.self="B"),
    definition=cmpfun(function(  
        .self,
        ...
    ){
    .self$b.2
    })
)

Instances

x.b <- new("B", b.1=10, b.2=TRUE)
x.a <- new("A", a.1=x.b, a.2="hello world")

Public vs. private

Instances of class A (i.e. x.a) should be allowed to use class B's public methods:

> x.a$a.1$thisIsPublic()
[1] 10000

Instances of class A (i.e. x.a) should not be allowed to use class B's private methods. So I would want this not to work, i.e. result in an error:

> x.a$a.1$thisIsPrivate()
[1] TRUE

Any idea how one could specify this?

The only thing I came up with so far:

Adding a sender argument to each method, explicitly specify it for each method call and check if class(.self) == class(sender). But that seems a bit “explicit“.

Rappster
  • 12,762
  • 7
  • 71
  • 120
  • x.a is an instance of class A, but x.a$a.1 is an instance of class B. You want to stop an instance of class B accessing class B's private methods? You are probably going to enter a whole world of pain trying to stop a class from accessing its methods based on what kind of data structure it might happen to live in... – Spacedman Aug 26 '12 at 07:17
  • Totally true and that's not what I'm aiming at. Again this is a topic where I feel I'm just lacking some background knowledge concerning OOP. Placing instances of certain classes in fields of other classes (i.e. `x.a$a.1` as an instance of class `B` in `x.a` of class `A`) has just been my way of implementing some degreee of encapsulation. But you're totally right that this way it's not really possible to distinguish between public and private methods as in the end it's `a.1` that's calling the method, not `x.a`. I'll think about a good update to my example to make things clearer. – Rappster Aug 28 '12 at 14:04

2 Answers2

8

As functions are first-class objects in R, you can embed one inside the other, as follows:

hello <- function() {
    print_ <- function() { 
         return ('hello world')
    }
    print_()
}

Yes, it's cheeky, probably not the cleanest way, but it does work... Invoke using 'hello()'.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
hd1
  • 33,938
  • 5
  • 80
  • 91
  • 3
    I don't think it's cheeky, at all. This is pretty standard practice in javascript libraries, and I wouldn't be surprised to see it in other function-oriented scripting languages. – 16807 Jun 14 '16 at 20:22
3

The short answer is to make a package. R's object systems and it's means of partitioning code (namespaces) are more separate than their equivalents in Java-like languages.

When you make a package, you specify what gets exported in a file called NAMESPACE using directives export and exportMethods. You can choose not to export methods and other R objects that you wish to be package private (to use Java terminology). See the Namespaces with S4 classes and methods section of the Writing R Extensions manual

Making a package is tricky the first time you do it, but there's lot's of help. See the docs for package.skeleton and the Writing R Extensions manual linked above.

Make sure Reference classes are really what you want. Regular S4 classes are usually the more R-ish way, for whatever that's worth. A great source of information about R's many OO constructs (and about packaging, too) is on Hadley Wickham's devtools wiki.

cbare
  • 12,060
  • 8
  • 56
  • 63
  • 3
    I completely agree with this, packages are the way to go. Note, however, that not exporting something form a package does not mean that the user cannot access it. You can still access "private" functions and data with the '`:::`' operator. – Gabor Csardi Aug 31 '12 at 02:04