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()
or its short versoin
set_opt()
:
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.
$a
[1] 1
$b
[1] "text"
[1] 1
[1] 1
$a
[1] 1
$b
[1] "text"
$a
[1] 2
$b
[1] "new text"
$a
[1] 2
$b
[1] ""
$a
[1] 1
$b
[1] "text"
opt
generated by set_opt()
contains an
argument RESET
which is used to reset the options to the
default:
$a
[1] 1
$b
[1] "text"
Simply printing opt
gives a summary of all options.
Option Value
------:-------
a 1
b text
If option values are set as lists, more configurations can be customized.
There are two basic fields that are used to check the input option values:
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.
Error: Length of 'a' should be one of 1, 3.
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 = set_opt(
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.
$a
[1] 1
$b
[1] 2
$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 = set_opt(
a = list(.value = 1,
.validate = function(x) x > 0 && x < 10
)
)
opt(a = 20) # This will cause an error
Error: a didn't pass the validation. Your option is invalid.
.failed_msg
is used to configure the error message once
validation is failed.
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
Error: a 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 = 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")
[1] FALSE
[1] FALSE
[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 = 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")
[1] 2 2 2 2
[1] 2 4 2 4
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.
opt = set_opt(
prefix = ""
)
opt(prefix = function() paste("[", Sys.time(), "] ", sep = " "))
opt("prefix") # or opt$prefix
[1] "[ 2024-10-31 21:08:04.495303 ] "
[1] "[ 2024-10-31 21:08:06.499147 ] "
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 = 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
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 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
.
[1] 2
[1] 4
However, you can still overwrite option b
:
$a
[1] 2
$b
[1] 3
.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
.
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")
[1] 2
Error: b 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
.
[1] 2
[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.
[1] 2
[1] 1
[1] 4
[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 = set_opt(
a = 1
)
f1 = function() {
opt(LOCAL = TRUE)
opt(a = 2)
return(f2())
}
f2 = function() {
opt$a
}
f1()
[1] 2
[1] 1
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.
$old
[1] 1
$new
[1] 1
$old
[1] 2
$new
[1] 2
$old
[1] 3
$new
[1] 3
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.
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
Option Value
------:------------------------------------------------------------------------
a 1
b b
c a, b, c, d
(26 letters)
d 0, 0
(start points in the unit circle)
e a user-defined function
(distribution to generate random numbers and a very long long long
long long long long long text)
Use dump_opt()
to get summary for each option.
Field Value
name a
default_value 1
current_value 1
length no limit
class no limit
validate a user-defined function
failed_msg Your option is invalid.
filter a user-defined function
read.only FALSE
private FALSE
visible TRUE
description ""
__generated_namespace__ R_GlobalEnv
Field Value
name d
default_value 0, 0
current_value 0, 0
length no limit
class numeric
validate a user-defined function
failed_msg The point should be in the unit circle
filter a user-defined function
read.only FALSE
private FALSE
visible TRUE
description start points in the unit circle
__generated_namespace__ R_GlobalEnv
New options can be added after the option function is created by
explicitely specifying ADD = TRUE
:
Option Value
------:-------
a 1
b 2
Note you cannot add new options by using $
(or more
precisely $<-
) operator because $
can only
access options that have already been created.
Error: No such option: 'c'. If you want to add this new option, please use
your_opt_fun(c = ..., ADD = TRUE)
Like using a complex configuration list when creating a new option in
set_opt()
, here you can also use configuration list with
ADD = TRUE
.
Option Value
------:-------
a 1
b 2
c c
Error: Class of 'c' should be 'character'.
Of course you can put more than one options in opt()
when adding them.
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.
$b
[1] 2
[1] 1
[1] 2
$b
[1] 2
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.
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.
Error: 'a' is a private option and it can only be modified inside 'stats'
namespace while not 'R_GlobalEnv'.
But you can still access it.
[1] 1
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:
function (..., RESET = FALSE, READ.ONLY = NULL, LOCAL = FALSE,
ADD = FALSE)
NULL
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.
[1] 2
[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.
$list
$list$a
[1] 1
$list$b
[1] 2
$list
$list$a
[1] 1
$list$b
[1] 2
Error: Class of 'list' should be 'list'.
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.
Warning: Your definition for 'a' is mixed. It should only contain .value,
.class, .length, .validate, .failed_msg, .filter, .read.only, .private,
.visible, .synonymous, .description. Ignore the setting and use the
whole list as the default value.
$.value
[1] 1
$class
[1] "numeric"
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.
[1] -1
R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 24.04.1 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
[3] LC_TIME=en_US.UTF-8 LC_COLLATE=C
[5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=en_US.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
time zone: Etc/UTC
tzcode source: system (glibc)
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] GlobalOptions_0.1.2 GetoptLong_1.1.0 knitr_1.48
loaded via a namespace (and not attached):
[1] digest_0.6.37 R6_2.5.1 fastmap_1.2.0 xfun_0.49
[5] rjson_0.2.23 maketools_1.3.1 cachem_1.1.0 htmltools_0.5.8.1
[9] rmarkdown_2.28 buildtools_1.0.0 lifecycle_1.0.4 cli_3.6.3
[13] sass_0.4.9 jquerylib_0.1.4 compiler_4.4.1 sys_3.4.3
[17] tools_4.4.1 evaluate_1.0.1 bslib_0.8.0 yaml_2.3.10
[21] crayon_1.5.3 jsonlite_1.8.9 rlang_1.1.4