mathematical.coffee
mathematical.coffee

Reputation: 56955

R - deliberately mask a function in a package function?

I'm trying to mask a function that a function from a package calls.

As a reproducible (I think) example, look at the function isTRUE:

function (x) 
identical(TRUE, x)

Suppose for some reason I wanted identical to always return "foobar", and hence isTRUE would always return "foobar":

# try override identical
identical <- function(...) { return('foobar') }
identical(TRUE, 'a') # 'foobar', as expected

Now I call isTRUE, hoping that the call to identical in that function will access my masked version, but it doesn't:

isTRUE('a') # hope that this will return 'foobar'
# [1] FALSE

So in general, how do I temporarily cause a function called from within a packaged function to return something different?

Context

I have a function in my package:

myFunc <- function(...) {
    if (!require(rgdal)) {
        stop("You do not have rgdal installed")
    }
    # ...
}

I want to test that if rgdal is not installed, the function throws an error. However, I do have rgdal installed. I want myFunc to think it isn't (temporarily), so I am trying to do:

require <- function(...) { return(FALSE) }

before calling myFunc hoping that it will fail. However, it appears that myFunc isn't tricked by this and still calls base::require instead of my require.

(Yes, this seems like a trivial thing, because most certainly myFunc would throw an error if I didn't have rgdal installed, but suppose now the condition were more complex and I wanted to test in the same way - my question still stands)

Upvotes: 4

Views: 1303

Answers (2)

Kevin Jin
Kevin Jin

Reputation: 1606

I was experimenting with the same overrideIn trick that mnel and mathematical.coffee explained above and I ended up finding another alternative from here:

overRideIn3 <- function(fun,...){
    e <- environment()
    overrides <- list(...)
    ns <- nchar(names(overrides)) > 0L

    list2env(overrides[ns], envir = e) 

    environment(fun) <- e
    fun
}

Note that all of these dynamic scoping tricks only work on the scope of fun. That means that any helper functions that fun calls will not have access to the definitions provided in overrides unless fun also wraps those helper functions with overRideIn.

Upvotes: 0

mnel
mnel

Reputation: 115505

You can programatically create a function

foo <- function(...) if(!require('MASS')) stop('foo')

testfun <- function(fun){
  require <- function(...) FALSE
  fff <- function(){}
    formals(fff) <- formals(fun)
    body(fff) <- body(fun)
  fff

}

testfoo <- testfun('foo')

require is defined as is testfun when the function is now created.

foo()
## Loading required package: MASS

detach(package:MASS)

testfoo()
# Error in testfoo() : foo

You could do something similar with local, but I think it would be messier

eg

testfoo2 <- local({
  require <- function(...) FALSE
  foo <- function(...) if(!require('MASS')) stop('foo')
  })

testfoo2()
## Error in testfoo2() : foo

(From mathematicalcoffee - a followup based on this answer).

I was able to define a function:

overrideIn <- function(f, ...) {                                                
    overrides <- list(...)                                                      
    nms <- names(overrides)[names(overrides) != '']                             
    # stub out the functions                                                    
    for (nm in nms) {                                                           
        assign(nm, overrides[[nm]])                                             
    }                                                                           

    # copy over f                                                               
    fff <- function () {}                                                       
    formals(fff) <- formals(f)                                                  
    body(fff) <- body(f)                                                        
    return(fff)                                                                 
}

so that I could do

f <- overrideIn(myFunc, require=function (...) FALSE)

Now when I call f it has the overridden version of require in it, so I can do (using the fantastic testthat package):

expect_that(f(), throws_error('You do not have rgdal installed'))

A slightly more succinct version of the overide function (mnel again)

overRideIn2 <- function(fun,...){
   e <- environment()
   .list <- list(...)
   ns <- nchar(names(.list))>0L

   list2env(.list[ns], envir = e) 

   fff <- as.function(as.list(fun))

  }

Upvotes: 2

Related Questions