597 lines
15 KiB
Plaintext
597 lines
15 KiB
Plaintext
|
---
|
||
|
title: "Generate Global Options"
|
||
|
author: "Zuguang Gu (z.gu@dkfz.de)"
|
||
|
date: '`r Sys.Date()`'
|
||
|
output:
|
||
|
html_document:
|
||
|
fig_caption: true
|
||
|
toc: true
|
||
|
toc_depth: 3
|
||
|
toc_float: true
|
||
|
vignette: >
|
||
|
%\VignetteIndexEntry{Generate Global Options}
|
||
|
%\VignetteEngine{knitr::rmarkdown}
|
||
|
%\VignetteEncoding{UTF-8}
|
||
|
---
|
||
|
|
||
|
---------------------------------------------------------------------
|
||
|
|
||
|
```{r, echo = FALSE, message = FALSE}
|
||
|
library(knitr)
|
||
|
knitr::opts_chunk$set(
|
||
|
error = FALSE,
|
||
|
tidy = FALSE,
|
||
|
message = FALSE,
|
||
|
comment = NA,
|
||
|
fig.align = "center")
|
||
|
library(GetoptLong)
|
||
|
```
|
||
|
|
||
|
|
||
|
Global option function such as `options()` and `par()` provides a way to
|
||
|
control global settings. Here the **GlobalOptions** package provides a more
|
||
|
general and controlable way to generate such functions, which can:
|
||
|
|
||
|
1. validate the values (e.g. class, length and self-defined validations);
|
||
|
2. set read-only options;
|
||
|
3. set invisible options;
|
||
|
4. set private options which are only accessable in a certain namespace;
|
||
|
5. support local options and global option;
|
||
|
6. print options with explanations.
|
||
|
|
||
|
## General usage
|
||
|
|
||
|
The most simple use is to generate an option function with default values by
|
||
|
callling `setGlobalOptions()` or its short versoin `set_opt()`:
|
||
|
|
||
|
```{r}
|
||
|
library(GlobalOptions)
|
||
|
opt = set_opt(
|
||
|
a = 1,
|
||
|
b = "text"
|
||
|
)
|
||
|
```
|
||
|
|
||
|
|
||
|
The returned value `opt` is an option function which can be used to get or set
|
||
|
options. Options in `opt` can be accessed either by specifying as arguments or
|
||
|
by using the `$` operator.
|
||
|
|
||
|
```{r}
|
||
|
opt()
|
||
|
opt("a")
|
||
|
opt$a
|
||
|
op = opt()
|
||
|
op
|
||
|
opt(a = 2, b = "new text")
|
||
|
opt()
|
||
|
opt$b = ""
|
||
|
opt()
|
||
|
opt(op)
|
||
|
opt()
|
||
|
```
|
||
|
|
||
|
`opt` generated by `set_opt()` contains an argument `RESET` which is used to
|
||
|
reset the options to the default:
|
||
|
|
||
|
```{r}
|
||
|
opt(a = 2, b = "new text")
|
||
|
opt(RESET = TRUE)
|
||
|
opt()
|
||
|
```
|
||
|
|
||
|
Simply printing `opt` gives a summary of all options.
|
||
|
|
||
|
```{r}
|
||
|
opt
|
||
|
```
|
||
|
|
||
|
## Advanced usage
|
||
|
|
||
|
If option values are set as lists, more configurations can be customized.
|
||
|
|
||
|
### Simple validation
|
||
|
|
||
|
There are two basic fields that are used to check the input option values:
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = list(.value = 1,
|
||
|
.length = c(1, 3),
|
||
|
.class = "numeric")
|
||
|
)
|
||
|
```
|
||
|
|
||
|
In above code, `.value` is the default value for the option `a`. The length of
|
||
|
the value is controlled by `.length` and the length should be either 1 or 3.
|
||
|
The class of the value should be `numeric`. If the input value does not fit
|
||
|
these criterions, there will be an error. The value of `.length` or `.class`
|
||
|
is a vector and the checking will be passed if one of the value fits user's
|
||
|
input.
|
||
|
|
||
|
```{r error = TRUE, purl = FALSE}
|
||
|
opt(a = 1:2) # there will be error because the length is 2
|
||
|
opt(a = "text") # there will be error because the input is character
|
||
|
```
|
||
|
|
||
|
### Read-only options
|
||
|
|
||
|
The value can be set as read-only by `.read.only` field and modifying such
|
||
|
option will cause an error.
|
||
|
|
||
|
```{r error = TRUE, purl = FALSE}
|
||
|
opt = set_opt(
|
||
|
a = list(.value = 1,
|
||
|
.read.only = TRUE)
|
||
|
)
|
||
|
opt(a = 2) # there will be error because a is read-only
|
||
|
```
|
||
|
|
||
|
There is also a pre-defined argument `READ.ONLY` in `opt()` which controls
|
||
|
whether to return only the read-only options or not.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = list(.value = 1,
|
||
|
.read.only = TRUE),
|
||
|
b = 2
|
||
|
)
|
||
|
opt(READ.ONLY = TRUE)
|
||
|
opt(READ.ONLY = FALSE)
|
||
|
opt(READ.ONLY = NULL) # default, to return both
|
||
|
```
|
||
|
|
||
|
### User-defined validation
|
||
|
|
||
|
More customized validation of the option values can be controlled by
|
||
|
`.validate` field. The value of `.validate` should be a function. The input of
|
||
|
the validation function is the input option value and the function should only
|
||
|
return a logical value.
|
||
|
|
||
|
`a` should only between 0 and 10 in following example.
|
||
|
|
||
|
```{r error = TRUE, purl = FALSE}
|
||
|
opt = set_opt(
|
||
|
a = list(.value = 1,
|
||
|
.validate = function(x) x > 0 && x < 10
|
||
|
)
|
||
|
)
|
||
|
opt(a = 20) # This will cause an error
|
||
|
```
|
||
|
|
||
|
`.failed_msg` is used to configure the error message once validation is failed.
|
||
|
|
||
|
```{r error = TRUE, purl = FALSE}
|
||
|
opt = set_opt(
|
||
|
a = list(.value = 1,
|
||
|
.validate = function(x) x > 0 && x < 10,
|
||
|
.failed_msg = "'a' should be in (0, 10)."
|
||
|
)
|
||
|
)
|
||
|
opt(a = 20) # This will cause an error
|
||
|
```
|
||
|
|
||
|
### Filter the option values
|
||
|
|
||
|
Filtering on the option values can be controlled by `.filter` field. This is
|
||
|
useful when the input option value is not valid but it is not necessary to
|
||
|
throw errors. More proper way is to modify the value silently. For example,
|
||
|
there is an option to control whether to print messages or not and it should
|
||
|
be set to `TRUE` or `FALSE`. However, users may set some other type of values
|
||
|
such as `NULL` or `NA`. In this case, non-`TRUE` values can be converted to
|
||
|
logical values by `.filter`. Similar as `.validate`, the input value for
|
||
|
filter function is the input option value, and it should return a filtered
|
||
|
option value.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
verbose =
|
||
|
list(.value = TRUE,
|
||
|
.filter = function(x) {
|
||
|
if(is.null(x)) {
|
||
|
return(FALSE)
|
||
|
} else if(is.na(x)) {
|
||
|
return(FALSE)
|
||
|
} else {
|
||
|
return(x)
|
||
|
}
|
||
|
})
|
||
|
)
|
||
|
opt(verbose = FALSE); opt("verbose")
|
||
|
opt(verbose = NA); opt("verbose")
|
||
|
opt(verbose = NULL); opt("verbose")
|
||
|
```
|
||
|
|
||
|
Another example is when there is an option which controls four margin values
|
||
|
of a plot, the length of the value can either be 1, 2, or 4. With `.filter`,
|
||
|
length can be normaliezd to 4 consistently.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
margin =
|
||
|
list(.value = c(1, 1, 1, 1),
|
||
|
.length = c(1, 2, 4),
|
||
|
.filter = function(x) {
|
||
|
if(length(x) == 1) {
|
||
|
return(rep(x, 4))
|
||
|
} else if(length(x) == 2) {
|
||
|
return(rep(x, 2))
|
||
|
} else {
|
||
|
return(x)
|
||
|
}
|
||
|
})
|
||
|
)
|
||
|
opt(margin = 2); opt("margin")
|
||
|
opt(margin = c(2, 4)); opt("margin")
|
||
|
```
|
||
|
|
||
|
### Dynamic querying the option value
|
||
|
|
||
|
The input option value can be set dynamicly by setting it as a function. When
|
||
|
the option value is set as a function and class of the option is non-function,
|
||
|
it will be executed when querying the option. In the following example, the
|
||
|
`prefix` option corresponds to the prefix of log messages. The returned option
|
||
|
value is the string after the execution of the input function.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
prefix = ""
|
||
|
)
|
||
|
opt(prefix = function() paste("[", Sys.time(), "] ", sep = " "))
|
||
|
opt("prefix") # or opt$prefix
|
||
|
Sys.sleep(2)
|
||
|
opt("prefix")
|
||
|
```
|
||
|
|
||
|
If the value of the option is a real function and users don't want to execute
|
||
|
it, just set `.class` to contain `function`, then the function will be treated
|
||
|
as a simple value.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
test_fun = list(.value = function(x1, x2) t.test(x1, x2)$p.value,
|
||
|
.class = "function")
|
||
|
)
|
||
|
opt(test_fun = function(x1, x2) cor.test(x1, x2)$p.value)
|
||
|
opt("test_fun") # or opt$test_fun
|
||
|
```
|
||
|
|
||
|
### Interaction between options
|
||
|
|
||
|
The self-defined function (i.e. value function, validation function or filter
|
||
|
function) is applied per-option independently. But sometimes we want to set
|
||
|
one option based on values of other options. In this case, we need a function
|
||
|
which can get other option values. `.v()` can be used to access other option
|
||
|
values defined beforehand. `.v("a")` can also be written as `.v(a)` or `.v$a`.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = 1,
|
||
|
b = function() 2 * .v$a
|
||
|
)
|
||
|
opt("b") # or opt$b
|
||
|
opt(a = 2)
|
||
|
opt("b")
|
||
|
```
|
||
|
|
||
|
However, you can still overwrite option `b`:
|
||
|
|
||
|
```{r}
|
||
|
opt(a = 2, b = 3) # b was overwriiten and will not be 2*a
|
||
|
opt()
|
||
|
```
|
||
|
|
||
|
`.v` can also be used in `.validate` and `.filter` fields. In the second
|
||
|
example, sign of `b` should be as same as sign of `a`.
|
||
|
|
||
|
```{r error = TRUE, purl = FALSE}
|
||
|
opt = set_opt(
|
||
|
a = 1,
|
||
|
b = list(.value = 0,
|
||
|
.validate = function(x) {
|
||
|
if(.v$a > 0) x > 0
|
||
|
else x < 0
|
||
|
},
|
||
|
.filter = function(x) {
|
||
|
x + .v$a
|
||
|
},
|
||
|
.failed_msg = "'b' should have same sign as 'a'.")
|
||
|
)
|
||
|
opt(b = 1)
|
||
|
opt("b")
|
||
|
opt(a = 1, b = -1) # this should cause an error
|
||
|
```
|
||
|
|
||
|
### Local options
|
||
|
|
||
|
The option funtion also has a `LOCAL` argument which switches local mode and
|
||
|
global mode. When `LOCAL` is set to `TRUE`, a copy of current options is
|
||
|
generated and all queries are applied on the copy version. The local mode is
|
||
|
turned off when `LOCAL` is explicitely specified to `FALSE`.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = 1
|
||
|
)
|
||
|
|
||
|
opt(LOCAL = TRUE)
|
||
|
opt(a = 2)
|
||
|
opt$a
|
||
|
opt(LOCAL = FALSE)
|
||
|
opt$a
|
||
|
```
|
||
|
|
||
|
Local mode will be automatically turned off when enrivonment changes. In
|
||
|
following example, local mode only works inside `f1()` and `f2()` functions
|
||
|
and the local copies are independent in `f1()` and `f2()`. Note when leaving
|
||
|
e.g. `f1()`, the copy of the option is deleted.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = 1
|
||
|
)
|
||
|
|
||
|
f1 = function() {
|
||
|
opt(LOCAL = TRUE)
|
||
|
opt(a = 2)
|
||
|
return(opt$a)
|
||
|
}
|
||
|
f1()
|
||
|
opt$a
|
||
|
|
||
|
f2 = function() {
|
||
|
opt(LOCAL = TRUE)
|
||
|
opt(a = 4)
|
||
|
return(opt$a)
|
||
|
}
|
||
|
f2()
|
||
|
opt$a
|
||
|
```
|
||
|
|
||
|
If `f1()` calls `f2()`, `f2()` will be in the same local mode as `f1()`. In other word,
|
||
|
all children frames are in a same local mode if the parent frame is in local mode.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = 1
|
||
|
)
|
||
|
|
||
|
f1 = function() {
|
||
|
opt(LOCAL = TRUE)
|
||
|
opt(a = 2)
|
||
|
return(f2())
|
||
|
}
|
||
|
|
||
|
f2 = function() {
|
||
|
opt$a
|
||
|
}
|
||
|
|
||
|
f1()
|
||
|
opt$a
|
||
|
```
|
||
|
|
||
|
### Synonymous options
|
||
|
|
||
|
It can be possible that several weeks later, developers have better names for
|
||
|
the options. They want to use the new option names but still do not want to
|
||
|
disable the old ones. In this case, `.synonymous` field can be set to let the
|
||
|
new option and old option reference to a same internal option object (which
|
||
|
means all other configuration specified for this option is ignored). The change of
|
||
|
values of either one will also affect the companions correspondingly.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
old = 1,
|
||
|
new = list(.value = 1,
|
||
|
.synonymous = "old")
|
||
|
)
|
||
|
opt()
|
||
|
opt$old = 2
|
||
|
opt()
|
||
|
opt$new = 3
|
||
|
opt()
|
||
|
```
|
||
|
|
||
|
### Print the object
|
||
|
|
||
|
There is a `.description` field for each option which is only used when
|
||
|
printing the summary of options. As shown before, simply entering the option
|
||
|
object gives a summary table for all options.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = 1,
|
||
|
b = "b",
|
||
|
c = list(.value = letters[1:4],
|
||
|
.class = "character",
|
||
|
.description = "26 letters"),
|
||
|
d = list(.value = c(0, 0),
|
||
|
.class = "numeric",
|
||
|
.validate = function(x) x[1]^2 + x[2]^2 <= 1,
|
||
|
.failed_msg = "The point should be in the unit circle",
|
||
|
.description = "start points in the unit circle"),
|
||
|
e = list(.value = rnorm,
|
||
|
.class = "function",
|
||
|
.description = "distribution to generate random numbers and a very long long long long long long long long text")
|
||
|
)
|
||
|
opt
|
||
|
```
|
||
|
|
||
|
Use `dump_opt()` to get summary for each option.
|
||
|
|
||
|
```{r}
|
||
|
dump_opt(opt, "a")
|
||
|
dump_opt(opt, "d")
|
||
|
```
|
||
|
|
||
|
### Add new options
|
||
|
|
||
|
New options can be added after the option function is created by explicitely
|
||
|
specifying `ADD = TRUE`:
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(a = 1)
|
||
|
opt(b = 2, ADD = TRUE)
|
||
|
opt
|
||
|
```
|
||
|
|
||
|
Note you cannot add new options by using `$` (or more precisely `$<-`)
|
||
|
operator because `$` can only access options that have already been created.
|
||
|
|
||
|
```{r, error = TRUE, purl = FALSE}
|
||
|
opt$c = 3
|
||
|
```
|
||
|
|
||
|
Like using a complex configuration list when creating a new option in `set_opt()`,
|
||
|
here you can also use configuration list with `ADD = TRUE`.
|
||
|
|
||
|
```{r, error = TRUE, purl = FALSE}
|
||
|
opt(c = list(.value = "c",
|
||
|
.class = "character"),
|
||
|
ADD = TRUE)
|
||
|
opt
|
||
|
opt$c = 1
|
||
|
```
|
||
|
|
||
|
Of course you can put more than one options in `opt()` when adding them.
|
||
|
|
||
|
## Features for package development
|
||
|
|
||
|
Two additional fields may be helpful when developing packages. `.visible`
|
||
|
controls whether options are visible to users. The invisible option can only
|
||
|
be queried or modified by specifying its option name (just like you can
|
||
|
only open the door with the correct unique key). This would be helpful if
|
||
|
users want to put some secret options while do not want others to access. Is
|
||
|
this case, they can assign names with complex strings like
|
||
|
`.__MY_PRIVATE_KEY__.` as their secret options and afterwards they can access
|
||
|
it with this special key.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = list(.value = 1,
|
||
|
.visible = FALSE),
|
||
|
b = 2
|
||
|
)
|
||
|
opt()
|
||
|
opt$a
|
||
|
opt$a = 2
|
||
|
opt$a
|
||
|
opt()
|
||
|
```
|
||
|
|
||
|
Another field `.private` controls whether the option is only private to the
|
||
|
namespace (e.g. packages). If it is set to `TRUE`, the option can only be
|
||
|
modified in the same namespace (or top environment) where the option function
|
||
|
is generated. E.g, if you are writing a package named **foo** and generating
|
||
|
an option function `foo_opt()`, by setting the option with `.private` to
|
||
|
`TRUE`, the value for such options can only be modified inside **foo** package
|
||
|
while it is not permitted outside **foo**. At the same time, private options
|
||
|
become read-only options if querying outside **foo** package.
|
||
|
|
||
|
In following example, we manually modify the namespace where
|
||
|
`set_opt()` is called in `stats` package.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = list(.value = 1,
|
||
|
.private = TRUE)
|
||
|
)
|
||
|
require(stats)
|
||
|
ns = getNamespace("stats")
|
||
|
environment(opt)$options$a$`__generated_namespace__` = ns
|
||
|
```
|
||
|
|
||
|
There will be error if trying to modify `a` which is private in `stats` namespace.
|
||
|
|
||
|
```{r error = TRUE, purl = FALSE}
|
||
|
opt$a = 2
|
||
|
```
|
||
|
|
||
|
But you can still access it.
|
||
|
|
||
|
```{r}
|
||
|
opt$a
|
||
|
```
|
||
|
|
||
|
|
||
|
The option object generated by `set_opt()` is actually a function. It contains four arguments:
|
||
|
`...`, `RESET`, `READ.ONLY`, `LOCAL`, `ADD`. If you want to put the option function
|
||
|
into a package, remember to document all the four arguments:
|
||
|
|
||
|
```{r}
|
||
|
args(opt)
|
||
|
```
|
||
|
|
||
|
## Misc
|
||
|
|
||
|
The order of validation when modifying an option value is `.read.only`,
|
||
|
`.private`, `.length`, `.class`, `.validate`, `.filter`, `.length`, `.class`.
|
||
|
Note validation on length and class of the option values will be applied again
|
||
|
after filtering.
|
||
|
|
||
|
Global options are stored in private environments. Each time when generating a
|
||
|
option function, there will be new environments created. Thus global options
|
||
|
will not conflict if they come from different option functions.
|
||
|
|
||
|
```{r}
|
||
|
opt1 = set_opt(
|
||
|
a = list(.value = 1)
|
||
|
)
|
||
|
opt2 = set_opt(
|
||
|
a = list(.value = 1)
|
||
|
)
|
||
|
opt1$a = 2
|
||
|
opt1$a
|
||
|
opt2$a
|
||
|
```
|
||
|
|
||
|
Note the option values can also be set as a list, so for the list containing configurations,
|
||
|
names of the field is started with a dot `.` to be distinguished from the normal list.
|
||
|
|
||
|
```{r error = TRUE, purl = FALSE}
|
||
|
opt = set_opt(
|
||
|
list = list(a = 1,
|
||
|
b = 2)
|
||
|
)
|
||
|
opt()
|
||
|
|
||
|
opt = set_opt(
|
||
|
list = list(.value = list(a = 1, b = 2),
|
||
|
.class = "list")
|
||
|
)
|
||
|
opt()
|
||
|
opt$list = 1 # this will cause an error
|
||
|
```
|
||
|
|
||
|
If you made a type of the field names when configurating the options (e.g.
|
||
|
forgot to type the leading dot), there will be a warning and the whole
|
||
|
configuration list is treated as a normal list for this option.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = list(.value = 1,
|
||
|
class = "numeric") # <- here it should be .class
|
||
|
)
|
||
|
opt$a
|
||
|
```
|
||
|
|
||
|
|
||
|
The final and the most important thing is the validation by `.class`,
|
||
|
`.length`, `.validate`, `.filter` will not be applied on default values
|
||
|
because users who design their option functions should know whether the
|
||
|
default values are valid or not.
|
||
|
|
||
|
```{r}
|
||
|
opt = set_opt(
|
||
|
a = list(.value = -1,
|
||
|
.validate = function(x) x > 0)
|
||
|
)
|
||
|
opt$a
|
||
|
```
|
||
|
|
||
|
## Session info
|
||
|
|
||
|
```{r}
|
||
|
sessionInfo()
|
||
|
```
|