--- title: "Introduction to settings" author: "Mark van der Loo" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: toc: true vignette: > %\VignetteIndexEntry{Introduction to settings} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) library(settings) ``` ### What are options? 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. ### What does the settings package do? 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. - Define and manage option sets for your package or application with ease - Create option managers that can validate user-defined options in a flexible way. - Implement multiple levels of local and global options if so desired - As a package author, you have the option to export a `reset` function, allowing your users to reset options to their defaults. ### Basic option settings management To start storing options we first define an options manager by feeding it default option names and values. ```{r} my_options <- options_manager(foo = 1, bar = 2, baz = 'hello') my_options() ``` 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. ```{r} my_options('foo') my_options('foo','baz') ``` Option values may also be set as follows. ```{r} my_options(foo=7) my_options() # or multiple options at once my_options(foo=7,bar=0) my_options() ``` And we reset everything to factory settings. ```{r} reset(my_options) my_options() ``` ### 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. ```{r} 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. ```{r, eval=FALSE} > 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](Validating_and_manipulating_option_values.html). ### 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. ```{r} 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. ```{r} 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. ```{r} # a and b are taken from global option set. f(1) # 2 + 3 * 1 # specify 'a' f(1,a=10) # 10 + 3 * 1 #specify 'a' and 'b' f(1,a=10,b=100) # 10 + 100 * 1 # global options are unaltered, as expected. my_options() ``` **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. ```{r,eval=FALSE} # 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 ```{r,eval=FALSE} #' 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. ```{r} # 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. ```{r} # 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```. ```{r} setGeneric("test_options",function(where=NULL,...) standardGeneric("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. ```{r} # instantiate a class; with global options as currently set. test <- TestClass() # get global options test_options() # set a global option test_options(foo=2) test_options('foo') # check that 'test' uses global option test_options(test) # set local option test <- test_options(test,bar=3) test_options(test) # check global option test_options() ``` An example Reference class with local options and global default ---------------------------------------------------------------- Again, we start by defining an options manager for the global scope. ```{r} opt <- options_manager(foo=1,bar=2) ``` The below reference class holds by default a reference to this manager. ```{r} 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. ```{r} reftest <- RefTest() reftest$options() # set global options opt(foo=10) reftest$options() # set local options reftest$options(bar=3) reftest$options() opt() # reset local options reftest$reset() reftest$options() ```