Making an R list as a JavaScript-style config object
I need a config object to store some constants in my R code. These are the url for the remote file, the directory for storing the uploaded files, local filenames to be used after upload, etc. I'm an R newbie, so I decided to use a JavaScript-like config object (especially a list), similar to something like this:
var config = {
wd : "/some/working/dir",
zipSrc : "http://www.example.com/remote_data_file.zip",
data_dir : "data",
data_zip : "data.zip",
data_txt : "data.txt",
abs_data_dir : this.wd + this.data_dir,
abs_data_zip : this.abs_data_dir + this.data_zip,
abs_data_txt : this.abs_data_dir + this.data_txt
};
Note that with "this" it is possible to reference the current object and do some calculations / assignments right inside it.
The most distant place I have reached in R:
config <- list(wd = getwd(),
zipSrc = "http://www.example.com/remote_data_file.zip",
data_dir = "data",
data_zip = "data.zip",
data_txt = "data.txt")
But what if you still need to compute abs_data_dir and other similar values? Is it possible to make this work while "creating" the config list object? Is it possible to infer new named list items based on those just created, for example using mutate () in the dplyr package? If there is any JS analogue in R '"this" for referencing the current object?
source to share
Solution 1
within
comes to mind, I came to this after playing with it a bit:
config = rev(within(list(), {
wd = "/some/working/dir"
zipSrc = "http://www.example.com/remote_data_file.zip"
data_dir = "data"
data_zip = "data.zip"
data_txt = "data.txt"
abs_data_dir = file.path(wd, data_dir)
abs_data_zip = file.path(abs_data_dir, data_zip)
abs_data_txt = file.path(abs_data_dir, data_txt)
}))
config
# $wd
# [1] "/some/working/dir"
# $zipSrc
# [1] "http://www.example.com/remote_data_file.zip"
# $data_dir
# [1] "data"
# $data_zip
# [1] "data.zip"
# $data_txt
# [1] "data.txt"
# $abs_data_dir
# [1] "/some/working/dir/data"
# $abs_data_zip
# [1] "/some/working/dir/data/data.zip"
# $abs_data_txt
# [1] "/some/working/dir/data/data.txt"
You can turn this into a function:
list.eval <- function(...) rev(within(list(), ...))
is said to be as follows: list.eval({a = 1; b = a})
.
Solution 2
Here's another function using closer to list
. It will also handle both named and unnamed arguments, such as list
does:
list.eval <- function(...) {
e <- new.env()
argl <- as.list(sys.call())[-1L]
naml <- names(argl)
.eval.assign <- function(expr, name) {
value <- eval(expr, envir = e)
if (name != "") assign(name, value, envir = e)
return(value)
}
setNames(Map(.eval.assign, argl, naml), naml)
}
z <- 10
list.eval(a = 1, b = z, c = a, 10)
# $a
# [1] 1
#
# $b
# [1] 10
#
# $c
# [1] 1
#
# [[4]]
# [1] 10
source to share
This may be less aesthetically pleasing than flodel solutions, but you can work with environments rather than lists, for example:
mk_config <- function (expr) {
env <- new.env()
evalq(expr, envir = env)
function (s) get(s, envir = env)
}
The syntax is less like this dplyr::mutate
: you just write a block of code:
CF_ <- mk_config({
wd <- "/some/working/dir"
zipSrc <- "http://www.example.com/remote_data_file.zip"
data_dir <- "data"
data_zip <- "data.zip"
data_txt <- "data.txt"
abs_data_dir <- file.path(wd, data_dir)
abs_data_zip <- file.path(abs_data_dir, data_zip)
abs_data_txt <- file.path(abs_data_dir, data_txt)
})
CF <- function (e) CF_(deparse(substitute(e)))
One of the possible benefits of this is that get
it fails with an error if you are missing a key and not a return NULL
:
CF(abs_data_txt)
## [1] "/some/working/dir/data/data.txt"
CF(abs_dtaa_txt)
## Error in get(s, envir = env) (from cf.R#4) : object 'abs_dtaa_txt' not found
## Calls: CF -> CF_ -> get
You can also load config options from a file with something like
local(source('config.R', local = TRUE), envir = env)
source to share