Author: Zuguang Gu ( z.gu@dkfz.de )
Date: 2017-05-20
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:
The most simple use is to generate an option function with default values by callling setGlobalOptions()
:
library(GlobalOptions)
opt = setGlobalOptions(
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.
opt()
## $a
## [1] 1
##
## $b
## [1] "text"
opt("a")
## [1] 1
opt$a
## [1] 1
op = opt()
op
## $a
## [1] 1
##
## $b
## [1] "text"
opt(a = 2, b = "new text")
opt()
## $a
## [1] 2
##
## $b
## [1] "new text"
opt$b = ""
opt()
## $a
## [1] 2
##
## $b
## [1] ""
opt(op)
opt()
## $a
## [1] 1
##
## $b
## [1] "text"
opt
generated by setGlobalOptions()
contains an argument RESET
which is used to reset the options to the default:
opt(a = 2, b = "new text")
opt(RESET = TRUE)
opt()
## $a
## [1] 1
##
## $b
## [1] "text"
If option values are set as lists, more controls can be customized.
There are two basic fields that are used to check the input option values:
opt = setGlobalOptions(
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.
opt(a = 1:2) # there will be error because the length is 2
## Error: Length of 'a' should be one of 1, 3.
opt(a = "text") # there will be error because the input is character
## Error: Class of 'a' should be 'numeric'.
The value can be set as read-only by .read.only
field and modifying such option will cause an error.
opt = setGlobalOptions(
a = list(.value = 1,
.read.only = TRUE)
)
opt(a = 2) # there will be error because a is read-only
## Error: 'a' is a read-only option.
There is also a pre-defined argument READ.ONLY
in opt()
which controls whether to return only the read-only options or not.
opt = setGlobalOptions(
a = list(.value = 1,
.read.only = TRUE),
b = 2
)
opt(READ.ONLY = TRUE)
## $a
## [1] 1
opt(READ.ONLY = FALSE)
## $b
## [1] 2
opt(READ.ONLY = NULL) # default, means return both
## $a
## [1] 1
##
## $b
## [1] 2
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.
opt = setGlobalOptions(
a = list(.value = 1,
.validate = function(x) x > 0 && x < 10
)
)
opt(a = 20) # This will cause an error
## Error: Didn't pass the validation. Your option is invalid.
.failed_msg
is used to configure the error message once validation is failed.
opt = setGlobalOptions(
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
## Error: Didn't pass the validation. 'a' should be in (0, 10).
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.
opt = setGlobalOptions(
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")
## [1] FALSE
opt(verbose = NA); opt("verbose")
## [1] FALSE
opt(verbose = NULL); opt("verbose")
## [1] FALSE
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.
opt = setGlobalOptions(
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")
## [1] 2 2 2 2
opt(margin = c(2, 4)); opt("margin")
## [1] 2 4 2 4
The input option value can be set as dynamic 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.
opt = setGlobalOptions(
prefix = ""
)
opt(prefix = function() paste("[", Sys.time(), "] ", sep = " "))
opt("prefix")
## [1] "[ 2017-05-20 09:37:43 ] "
Sys.sleep(2)
opt("prefix")
## [1] "[ 2017-05-20 09:37:45 ] "
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.
opt = setGlobalOptions(
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")
## function(x1, x2) cor.test(x1, x2)$p.value
The self-defined function (i.e. value function, validation function or filter function)
is applied per-option. 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. If get_opt_value_fun
argument is set to TRUE
, setGlobalOptions()
will return a list of two functions in which
the first one is the option function and the second one is used to extract other option values.
In following example, default value of option b
is two times the value of option a
.
lt = setGlobalOptions(
a = list(.value = 1),
b = list(.value = function() 2 * get_opt_value('a')),
get_opt_value_fun = TRUE
)
opt = lt$opt_fun
# the variable name here should be same as the one in above
get_opt_value = lt$get_opt_value
opt("b")
## [1] 2
opt(a = 2)
opt("b")
## [1] 4
opt(a = 2, b = 3)
opt()
## $a
## [1] 2
##
## $b
## [1] 3
And in the second example, sign of b
should be as same as sign of a
.
lt = setGlobalOptions(
a = list(.value = 1),
b = list(.value = 0,
.validate = function(x) {
if(get_opt_value('a') > 0) x > 0
else x < 0
},
.filter = function(x) {
x + get_opt_value('a')
},
.failed_msg = "'b' should have same sign as 'a'."),
get_opt_value_fun = TRUE
)
opt = lt$opt_fun
get_opt_value = lt$get_opt_value
opt(b = 1)
opt("b")
## [1] 2
opt(a = 1, b = -1) # this should cause an error
## Error: Didn't pass the validation. 'b' should have same sign as 'a'.
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
.
opt = setGlobalOptions(
a = 1
)
opt(LOCAL = TRUE)
opt(a = 2)
opt("a")
## [1] 2
opt(LOCAL = FALSE)
opt("a")
## [1] 1
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.
opt = setGlobalOptions(
a = 1
)
f1 = function() {
opt(LOCAL = TRUE)
opt(a = 2)
return(opt("a"))
}
f1()
## [1] 2
opt$a
## [1] 1
f2 = function() {
opt(LOCAL = TRUE)
opt(a = 4)
return(opt("a"))
}
f2()
## [1] 4
opt$a
## [1] 1
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.
opt = setGlobalOptions(
a = 1
)
f1 = function() {
opt(LOCAL = TRUE)
opt(a = 2)
return(f2())
}
f2 = function() {
opt("a")
}
f1()
## [1] 2
opt("a")
## [1] 1
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 single
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.
opt = setGlobalOptions(
a = list(.value = 1,
.visible = FALSE),
b = 2
)
opt()
## $b
## [1] 2
opt("a")
## [1] 1
opt(a = 2)
opt("a")
## [1] 2
opt()
## $b
## [1] 2
Another field .private
controls whether the option is only private to the namespace (e.g. package).
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 setGlobalOptions()
is called to stats
package.
opt = setGlobalOptions(
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.
opt$a = 2
## Error: 'a' is a private option and it can only be modified inside 'stats' namespace while not
## 'R_GlobalEnv'.
The option function generated by setGlobalOptions()
contains four arguments:
...
, RESET
, READ.ONLY
, LOCAL
. If you want to put the option function into a
package, remember to document all the four arguments:
args(opt)
## function (..., RESET = FALSE, READ.ONLY = NULL, LOCAL = FALSE)
## NULL
You should put GlobalOptions (>= 0.0.10)
to the Depends
field in your DESCRIPTION file
if you want users to access the options by the $
and $<-
operator. If not, they can
only access the options by the normal opt(name)
and opt(name = value)
way.
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.
opt1 = setGlobalOptions(
a = list(.value = 1)
)
opt2 = setGlobalOptions(
a = list(.value = 1)
)
opt1(a = 2)
opt1("a")
## [1] 2
opt2("a")
## [1] 1
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.
opt = setGlobalOptions(
list = list(a = 1,
b = 2)
)
opt()
## $list
## $list$a
## [1] 1
##
## $list$b
## [1] 2
opt = setGlobalOptions(
list = list(.value = list(a = 1, b = 2),
.class = "list")
)
opt()
## $list
## $list$a
## [1] 1
##
## $list$b
## [1] 2
opt(list = 1) # this will cause an error
## Error: Class of 'list' should be 'list'.
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.
sessionInfo()
## R version 3.3.2 (2016-10-31)
## Platform: x86_64-apple-darwin13.4.0 (64-bit)
## Running under: macOS Sierra 10.12.3
##
## locale:
## [1] C/en_US.UTF-8/C/C/C/C
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] GlobalOptions_0.0.12 knitr_1.15.16 markdown_0.7.7
##
## loaded via a namespace (and not attached):
## [1] magrittr_1.5 tools_3.3.2 stringi_1.1.2 stringr_1.2.0 evaluate_0.10