---
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()
```




