Inheritance and R expressions

Defaults and inheritance

The default configuration provides a set of values to use when no named configuration is active. Other configurations automatically inherit all default values so need only define values specialized for that configuration.

For example, in this configuration the production configuration doesn’t specify a value for trials so it will be read from the default configuration:

config.yml

default:
  trials: 5
  dataset: "data-sampled.csv"
  
production:
  dataset: "data.csv"

R

config::get(config = "production")
#> $trials
#> [1] 5
#> 
#> $dataset
#> [1] "data.csv"

All configurations automatically inherit from the default configuration. Configurations can also inherit from one or more other named configurations. For example, in this file the production configuration inherits from the test configuration:

config.yml

default:
  trials: 5
  dataset: "data-sampled.csv"

test:
  trials: 30
  dataset: "data-test.csv"
  
production:
  inherits: test
  dataset: "data.csv"

R

config::get(config = "production")
#> $trials
#> [1] 30
#> 
#> $inherits
#> [1] "test"
#> 
#> $dataset
#> [1] "data.csv"

Using R code inside the yaml file

You can execute R code within configuration files by prefacing values with !expr. This could be useful in cases where you want to base configuration values on environment variables, R options, or even other config files. For example:

config.yml

default:
  cores: 2
  debug: true
  server: "localhost:5555"
   
production:
  cores: !expr getOption("mc.cores")
  debug: !expr Sys.getenv("ENABLE_DEBUG") == "1"
  server: !expr config::get("server", file = "etc/server-config.yml")

The default result:

R

config::get("server")
#> [1] "localhost:5555"

The production result will depend on the environment variables on the server:

server-config.yml

cat(readLines("etc/server-config.yml"), sep = "\n")
#> default:
#>   server: "production.example.com"

R

config::get("server", config = "production")
#> [1] "production.example.com"

Referencing previously assigned parameters

You can use any previously assigned parameter inside of R code so long as it is assigned directly.

config.yml

default:
  file: "data.csv"

test:
  data_dir: "test/out"
  dataset: !expr file.path(data_dir, file)
  
production:
  data_dir: "production/out"
  dataset: !expr file.path(data_dir, file)

R

config::get("dataset", config = "test")
#> [1] "test/out/data.csv"

R

config::get(config = "production")
#> $file
#> [1] "data.csv"
#> 
#> $data_dir
#> [1] "production/out"
#> 
#> $dataset
#> [1] "production/out/data.csv"

Limitations of using R expressions

The ability to use R expressions gives substantial flexibility to allow configurations to depend on environment variables, files and other information that’s available in the target environment.

However, keep in mind these limitations:

  • Any R expression can only refer to elements in the configuration file that it inherits from.
  • An R expression can not refer to another R expression.

If the config file violates these limitations, config::get() will throw an error.

As an example, let’s say you want to construct a configuration that computes a location based on a filename and a folder.

A valid example

If both filename and folder are constant (i.e. not computed expressions) this works:

config.yml

default:
  filename: "trials.csv"
  folder: "some/path"
  location: !expr file.path(folder, filename)

R

config::get("location")
#> [1] "some/path/trials.csv"

An invalid example

However, if folder is also a computed value, then location can not be computed and this throws an error:

config.yml

default:
  filename: "trials.csv"
  folder: !expr c("some/path")
  location: !expr file.path(folder, filename)

R

config::get("location")
#> Error in config::get("location"): Attempt to assign nested list value from expression.
#> Only directly assigned values can be used in expressions.
#> Original Error:
#> *  eval(expr, envir = envir): object 'folder' not found