# Option settings management with the settings package

Mark van der Loo, 2015-10-27

The settings package is aimed to bring easy-to-use, yet powerful options management to R. In particular,

• for users it allows for an easy way to reset par and options to their factory defaults'.
• for users it brings an easy way to define option sets, alter their values and reset option values again to defaults;
• for R-programmers and package developers it offers the possibility to easily implement multiple levels of local vs global option settings.

## Resetting par and options

Here's an example of resetting graphical parameters.

library(settings)
par('lwd')

## [1] 1

par(lwd=4)
par('lwd')

## [1] 4

reset_par()
par('lwd')

## [1] 1


A call to reset_par() resets sends every graphical option back to its initial value. Use

reset_options()


to send all of base R's default options back to their beginnings.

## Basic option settings management

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. my_options('foo')  ## [1] 1  my_options('foo','baz')  ##$foo
## [1] 1
##
## $baz ## [1] "hello"  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. reset(my_options) my_options()  ##$foo
## [1] 1
##
## $bar ## [1] 2 ## ##$baz
## [1] "hello"


## Limiting options

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.

## Global versus local options

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.

my_options <- options_manager(a=2,b=3)


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.

## Using the 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

• If you depend the package on options, then reset (and all other functions) are available immediately for the user (this is not recommended).
• If you import the package you have to export reset explicitly if you want to expose it. This can be done for example by
#' Reset global options for pkg
#'
#' @export
pkg_reset() reset(MYPKGOPTIONS)


## An example S4 class with local options and global default

First we define a general options manager. If this is part of a package, this will in general be invisible to the user.

# general options manager, will be invisible to user.
opt <- options_manager(foo=1,bar=2)


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(...)))
})

## [1] "test_options"

# 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(...)
}
})

## [1] "test_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


## An example Reference class with local options and global default

Again, we start by defining an options manager for the global scope.

opt <- options_manager(foo=1,bar=2)


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