3

I am developing my first package and it is aimed at users who are new to R, so I am trying to minimize the amount of R skills required to use the package. As a result I want a function that changes defaults in other functions within my package. But I get the following error "cannot add bindings to a locked environment", which means the environment of the package is locked and I am not allowed to change the default values of its functions.

Here is an example that throws a similar error:

library(ggplot2)
assign(formals(geom_point)$position, "somethingelse", pos="package:ggplot2")

When I try assignInNamespace i get: Error in bindingIsLocked(x, ns) : no binding for "identity"

assignInNamespace(formals(geom_point)$position,"somethingelse", pos = "package:ggplot2")

Here is an example of what I hope to achieve.

default <- function(x=c("A", "B", "C")){
       x
       }
default()

change.default <- function(x){
formals(default)$x <<- x        # Notice the global assign
}

change.default(1:3)
default()

I am aware that this is far from the recommended approach, but I am willing to cut corners to improve the learning curve of the package. Is there a way to achieve this?

This question has been marked as a duplicate of Setting Function Defaults R on a Project Specific Basis. This is a different situation as this question concerns how to allow the user in a interactive session to change the defaults of a function - not how to actually do it. The old question could not have been solved with the options() function and it is therefore a different question.

Community
  • 1
  • 1
Anton Grau
  • 90
  • 6

2 Answers2

4

I think the colloquial way to achieve what you want is via option and packages in fact do so, e.g., lattice (although they use special options) or ascii.

Furthermore, this is also done so in base R, e.g., the famous and notorious default for stringsAsFactors.

If you look at ?read.table or ?data.frame you get: stringsAsFactors = default.stringsAsFactors(). Inspecting this reveals:

> default.stringsAsFactors
function () 
{
    val <- getOption("stringsAsFactors")
    if (is.null(val)) 
        val <- TRUE
    if (!is.logical(val) || is.na(val) || length(val) != 1L) 
        stop("options(\"stringsAsFactors\") not set to TRUE or FALSE")
    val
}
<bytecode: 0x000000000b068478>
<environment: namespace:base>

The relevant part here is getOption("stringsAsFactors") which produces:

> getOption("stringsAsFactors")
[1] TRUE

Changing it is achieved like this:

> options(stringsAsFactors = FALSE)
> getOption("stringsAsFactors")
[1] FALSE

To do what you want your package would need to set an option, and the function take it's values form the options. Another function could then change the options:

options(foo=c("A", "B", "C"))

default <- function(x=getOption("foo")){
    x
}
default()

change.default <- function(x){
    options(foo=x)
}

change.default(1:3)
default()

If you want your package to set the options when loaded, you need to create a .onAttach or .onLoad function in zzz.R. My afex package e.g., does this and changes the default contrasts. In your case it could look like the following:

.onAttach <- function(libname, pkgname) {
    options(foo=c("A", "B", "C"))
}

ascii does it via .onLoad (I don't remember what is the exact difference, but Writing R Extensions will help).

Henrik
  • 14,202
  • 10
  • 68
  • 91
3

Preferably, a function has the following things:

  1. Input arguments
  2. A function body which does something with those arguments
  3. Output arguments

So in your situation where you want to change something about the behavior of a function, changing the input arguments in the best way to go. See for example my answer to another post.

You could also use an option to save some global settings (e.g. which font to use, which PATH the packages you use are stored), see the answer of @James in the question I linked above. But use these things sparingly as it makes the code hard to read. I would primarily use them read only, i.e. set them once (either by the package or the user) and not allow functions to change them.

The unreadability stems from the fact that the behavior of the function is not solely determined locally (i.e. by the code directly working with it), but also by settings far away. This makes it hard to determine what a function does by purely looking at the code calling it, but you have to dig through much more code to fully understand what is going on. In addition, what if other functions change those options, making it even harder to predict what a given function will do as it depends on the history of functions. And here comes my earlier recommendation for read-only options back into play, if these are read only, some of the problems about readability are lessened.

Community
  • 1
  • 1
Paul Hiemstra
  • 59,984
  • 12
  • 142
  • 149