--- title: "Validating and manipulating options" author: "Mark van der Loo" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: toc: true vignette: > %\VignetteIndexEntry{Validating and manipulating options} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) library(settings) ``` With `settings` it is possible to create an options manager that extensively checks or manipulates the option values passed by a user. There are two use cases for this. First it allows you to create a useful error or warning when a user passes an option value that is incorrect. Second, it allows you to preprocess the option values before storing them. For example by casting a string to lower case. ### Example 1: hard-coded checks Suppose we want an option called `foo`, that can take three values: `boo`, `bar` and `baz`. We want to create an options manager that first casts the user value to lower case. Next, if the value is valid, the lower-case variant will be stored, otherwise an informative error is thrown. To do this, first write a function that performs these checks. Make sure that the function returns the value that needs to be stored. ```{r} foo_check <- function(x){ allowed <- c("boo","bar","baz") # cast to lowercase x <- base::tolower(x) if ( !x %in% allowed ){ stop("Option foo must be in 'boo', 'bar', or 'baz'", call. = FALSE) } else { x } } ``` Next, define the options manager as follows. ```{r} my_options <- options_manager(foo="boo", .allowed = list( foo = foo_check) ) my_options("foo") ``` It is important that the names in the `.allowed` list agree with the names of the options. Let's see what happens when we set the option with capitals: ```{r, error=TRUE} my_options(foo = "BaZ") my_options("foo") ``` Observe that the stored option is in lowercase whereas the user-defined value includes upper-case letters. ```{r,error=TRUE} my_options(foo=1) ``` ### Check function API The function that you write for checking and/or manipulating option values has to satisfy certain rules to make it work. It is _obligated_ for the function to 1. take a single argument as input. This will be the option value, passed by the user. 2. return a valid option value. It is _advised_ to make the cheking function throw an error (stop) when a user passes an invalid value. ### Example 2: general checkers In the above example, we have created a function where we hard-wired the possible values in the function code. Suppose you have two variables where you want to perform the same check, but with different values. In this example we show how to add this flexibility. We use the fact that in R, a function is a variable, just like any other R object. In particular this means that in R, a function can return other functions as output. We will use this to make a function called `make_checker` that creates a customized checker function for us. The input of this function are the allowed values. ```{r} make_checker <- function(allowed){ .allowed <- allowed function(x){ x <- tolower(x) if (!x %in% .allowed){ stop(sprintf("Allowed values are %s",paste0(.allowed, collapse=", ")), call.=FALSE) } else { x } } } ``` We now create an options manager with two options: `foo`, with valid values `hey` and `ho` and `bar`, with valid values `fee`, `fi`, `fo`, `fum`. ```{r} my_new_options <- options_manager(foo = "hey", bar="fee" , .allowed = list( foo = make_checker( c("hey", "ho") ) , bar = make_checker( c("fee", "fi", "fo", "fum") ) )) ``` Let's try it out: ```{r,error=TRUE} my_new_options(bar="FEE") my_new_options("bar") my_new_options(foo="do") ``` ### Exercises Here are some exercises to aquint yourself with this functionality. 1. Create an options manager with the option `boo`, with default value 1, where the allowed values are integer powers of two $(1, 2, 4,\ldots)$. If the user supplies a value that is not a power of two, an error is thrown. 2. Generalize the checker of excercise 1 to powers of $n$.