Software options are usually simple strings or numbers that alter the
behaviour of a programme, package or functions. Many options in R can be
set with the standard options
functions. However, sometimes
it is convenient to have richer option management facilities.
The settings
package is aimed to bring easy-to-use, yet
powerful options management to R. It is light-weight, consists of only a
few-hundred lines of code and does not import anything that does not
come with a standard R installation.
reset
function, allowing your users to reset options to
their defaults.To start storing options we first define an options manager by feeding it default option names and values.
my_options <- options_manager(foo = 1, bar = 2, baz = 'hello')
my_options()
#> $foo
#> [1] 1
#>
#> $bar
#> [1] 2
#>
#> $baz
#> [1] "hello"
You may pass an arbitrary number of such [key]=[value]
pairs. If you want to define an option without a default value, just set
its value to NULL
.
Individual values can be set and retrieved using the function
my_options
that we’ve just created.
Option values may also be set as follows.
my_options(foo=7)
my_options()
#> $foo
#> [1] 7
#>
#> $bar
#> [1] 2
#>
#> $baz
#> [1] "hello"
# or multiple options at once
my_options(foo=7,bar=0)
my_options()
#> $foo
#> [1] 7
#>
#> $bar
#> [1] 0
#>
#> $baz
#> [1] "hello"
And we reset everything to factory settings.
It is possible to limit the option values a user can set so you don’t have to check them at run time.
opt <- options_manager(foo="up", bar=2
, .allowed = list(
foo = inlist("up","down")
, bar = inrange(min=0, max=3)
)
)
In the above code, we set the following options:
foo
with default value "up"
and allowed
values "up"
and "down"
bar
with default value 2 and
0 <= bar <= 3
.If you try to set an invalid option, an error is produced.
> opt(foo="middle")
Error: Option value out of range. Allowed values are up, down
> opt(bar=7)
Error: Option value out of range. Allowed values are in [0, 3]
You don’t need to set allowed values or ranges for each and every
option. Only those options that have an entry in the
.allowed
list will be checked.
It is possible to customize the way options are checked or manipulated. This is explained in more detail in a second vignette.
It is nice when the behaviour of a function that depends on global
options can be altered at function call. With the settings
package you can create local options as follows. First, we create a
global options manager.
The following function uses global settings by default, but a user
can overwrite them by passing extra options as
[name]=[value]
pairs.
f <- function(x,...){
# create local copy of options, merged with the global options.
local_opts <- clone_and_merge(my_options,...)
# local options can be used
local_opts('a') + local_opts('b') * x
}
Now compare the following uses.
# a and b are taken from global option set.
f(1) # 2 + 3 * 1
#> [1] 5
# specify 'a'
f(1,a=10) # 10 + 3 * 1
#> [1] 13
#specify 'a' and 'b'
f(1,a=10,b=100) # 10 + 100 * 1
#> [1] 110
# global options are unaltered, as expected.
my_options()
#> $a
#> [1] 2
#>
#> $b
#> [1] 3
Note: the reset
function may also be
used to reset options in local_opts
within the definiton of
f
. This will not affect the global options.
settings
package as options manager for your
package.The easiest way is probably to create a file for example called
options.R
. Here’s an example.
# Variable, global to package's namespace.
# This function is not exported to user space and does not need to be documented.
MYPKGOPTIONS <- options_manager(a=1, b=2)
# User function that gets exported:
#' Set or get options for my package
#'
#' @param ... Option names to retrieve option values or \code{[key]=[value]} pairs to set options.
#'
#' @section Supported options:
#' The following options are supported
#' \itemize{
#' \item{\code{a}}{(\code{numeric};1) The value of a }
#' \item{\code{b}}{(\code{numeric};2) The value of b }
#' }
#'
#' @export
pkg_options <- function(...){
# protect against the use of reserved words.
stop_if_reserved(...)
MYPKGOPTIONS(...)
}
Here, we’ve introduced a new function called
stop_if_reserved
That is because a few words are for the
options
package’s internal use, see the documentation of
stop_if_reserved
for the list. All reserved words start
with .__
(dot-underscore-underscore) so the chance that a
user tries to use them is probably small. However, it’s always good to
be on the safe side.
Notes
depend
the package on options
, then
reset
(and all other functions) are available immediately
for the user (this is not recommended).import
the package you have to export
reset
explicitly if you want to expose it. This can be done
for example byFirst we define a general options manager. If this is part of a package, this will in general be invisible to the user.
Now, define a class where the prototype contains the global settings.
# class definition containing default options in prototype.
TestClass <- setClass("TestClass"
, slots=list(options='function',value='numeric')
, prototype = list(
options = opt
, value = 0
)
)
Note that a adding a function to an object is really adding a
reference (since each function has its own environment, which is a
reference object). For every instance of TestClass
where
the options
slot is the default, a call to
@options
is a call to the global opt
.
Now, we define a user-facing function that can set or get options,
eiter globally or specific to an instance of TestClass
.
setGeneric("test_options",function(where=NULL,...) standardGeneric("test_options"))
#> [1] "test_options"
# method for accessing global options
setMethod("test_options","ANY",function(where=NULL,...){
do.call(opt,c(where,list(...)))
})
# method for getting/setting functions in a slot.
setMethod("test_options","TestClass", function(where=NULL,...){
if (is_setting(...)){
where@options <- clone_and_merge(where@options,...)
where
} else {
where@options(...)
}
})
There are two things to note here. First of all we’ve introduced the
utility function is_setting
which determines if the
arguments in ...
are meant to set options
(TRUE
) or to get them (FALSE
). Secondly, note
that for the ANY
method, we need to merge the value of the
first argument.
Now let’s see how it all works out.
# instantiate a class; with global options as currently set.
test <- TestClass()
# get global options
test_options()
#> $foo
#> [1] 1
#>
#> $bar
#> [1] 2
# set a global option
test_options(foo=2)
test_options('foo')
#> [1] 2
# check that 'test' uses global option
test_options(test)
#> $foo
#> [1] 2
#>
#> $bar
#> [1] 2
# set local option
test <- test_options(test,bar=3)
test_options(test)
#> $foo
#> [1] 2
#>
#> $bar
#> [1] 3
# check global option
test_options()
#> $foo
#> [1] 2
#>
#> $bar
#> [1] 2
Again, we start by defining an options manager for the global scope.
The below reference class holds by default a reference to this manager.
RefTest <- setRefClass("RefTest"
, fields = list(.options='function',value='numeric')
, methods = list(
initialize = function(){
.self$.options <- opt
.self$value <- 0
}
, options = function(...){
if(is_setting(...)){
.self$.options <- clone_and_merge(.self$.options,...)
} else {
.self$.options(...)
}
}
, reset = function(){
# explicitly reference the 'settings' package here to avoid recursion.
settings::reset(.self$.options)
}
)
)
Note that we store the options in a field as if it was data, and not
a method, so we can manipulate it with RefTest
internal
methods. Here’s how it functions.
reftest <- RefTest()
reftest$options()
#> $foo
#> [1] 1
#>
#> $bar
#> [1] 2
# set global options
opt(foo=10)
reftest$options()
#> $foo
#> [1] 10
#>
#> $bar
#> [1] 2
# set local options
reftest$options(bar=3)
reftest$options()
#> $foo
#> [1] 10
#>
#> $bar
#> [1] 3
opt()
#> $foo
#> [1] 10
#>
#> $bar
#> [1] 2
# reset local options
reftest$reset()
reftest$options()
#> $foo
#> [1] 1
#>
#> $bar
#> [1] 2