--- title: "context" author: "Rich FitzJohn" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{context} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( error = FALSE, collapse = TRUE, comment = "#>") ``` The idea here is that we want to describe how to build a "context" and then evaluate one or more expressions in it. This is a little related to approaches like `docker` and `packrat` in that we want contexts to be isolated from one another, but different in that _portability_ is more important than isolation. Imagine that you have an analysis to run on another computer with: * packages to install from CRAN or any one of several other R package repositories (e.g., a `drat`, bioconductor, etc). * packages to install from GitHub * packages to install from local sources (e.g., private GitHub repos, unreleased code) * A number of source files to read in * A local environment to recreate (e.g., if calling a function from another function). The other computer may already have some packages installed, so you don't want to waste time and bandwidth re-installing them. So things end up littered with constructs like ```r if (!require("mypkg")) { install.packages("mypkg") library(mypkg) } ``` If these packages are coming from GitHub (or worse also have dependencies on GitHub) the bootstrap code gets out of hand very quickly and tends to be non-portable. Creating separate libraries (rather than sharing one from your personal computer) will be important if the architecture differs (e.g., you run Windows but you want to run code on a Linux cluster). The idea here is that `context` helps describe a context made from the above ingredients and then attempts to recreate it on a different computer (or in a different directory on your computer). ## Contexts A minimal context looks like this: ```{r} path <- tempfile() ctx <- context::context_save(path = path) ctx ``` Typically one would use the arguments `packages` and `sources` to describe the requirements of any tasks that you'll be running. ## Tasks Once a context is defined, *tasks* can be defined in the context. These are simply R expressions associated with the identifier of a context. ```{r } t <- context::task_save(quote(sin(1)), context = ctx) t ``` The task `t` above is just a key that can be used to retrieve information about the task later. ```{r} context::task_expr(t, ctx) ``` Several such tasks may exist, though here only one does ```{r} context::task_list(ctx) ``` To run a task we first need to "load" the context (this will actual load any required packages and source any scripts) then pass this through to `task_run` ```{r} res <- context::task_run(t, context::context_load(ctx)) ``` This prints the result of restoring the context and running the task: * `context`: the context id * `library`: calls to `library()` to load packages and attach namespaces * `namespace`: calls to `loadNamespace()`; these packages were present but not attached in the context. * `source`: There was nothing to `source()` here so this is blank, otherwise it would be a list of filenames. * `root`: the directory within which all our context/task files will be located * `context`: this is repeated here because we've finished the load part of the aove statement * `task`: the task id * `expr`: the expression to evaluate * `start`: start time * `ok`: indication of success * `end`: end time After all that, here is the result: ```{r} res ``` The result can also be retrieved using `task_result()`: ```{r} context::task_result(t, ctx) ``` This is not immensely useful as it is; it's just evaluation with more steps. Typically we'd do this in another process. You can do this with `callr` here: ```{r} res <- callr::rscript(file.path(path, "bin", "task_run"), c(path, t), echo = TRUE, show = TRUE) ```