Introduction
The concept of literate programming was pioneered by Donald Knuth in 1984 to improve the documentation of computer programs, as this famous quote explains:
I believe that the time is ripe for significantly better documentation of programs, and that we can best achieve this by considering programs to be works of literature. Hence, my title: “Literate Programming.”
The aim of literate programming is to create programs that are suitable literature for human beings rather than a set of instructions to a computer. A program becomes a web of two languages: a document formatting language, and a programming language. In Knuth’s original presentation, these were respectively TeX and Pascal. A program, or a set of programs, then process this web of code. Knuth developed the WEB system and coined the terms weaving for the process of creating the documentation, and tangling for the process of producing the executable program.
Friedrich Leisch brought literate programming to the R ecosystem with the Sweave framework. Based on the system noweb, Sweave allowed to combine text in LaTeX and R code for literate statistical practice and reporting. This sparkled the use of vignettes for improved documentation within R and contributed packages, and it certainly contributed to the rise of reproducible research practices in statistical computing. Sweave was also of course the inspiration for knitr, by far the most widely used literate programming system in the R community nowadays, if only as the basis of the document format R Markdown.
Although knitr provides more bells and whistles, Sweave remains simpler and, perhaps most importantly for the forthcoming discussion, it continues to give equal treatment to the lesser known of the two literate programming procedures: tangling.
Sweave was designed from the outset in a modular way to allow
different drivers for the weaving and tangling procedures. The
standard drivers are RweaveLatex to transform .Rnw files with
LaTeX documentation chunks and R code into .tex files, and Rtangle
to extract R code from a .Rnw file into .R scripts. A number of
packages propose additional drivers for Sweave, for example
ascii,
highlight or
R2HTML, just to name a
few; see also the Reproducible
Research CRAN
Task View.
The feature set of Sweave has remained remarkably stable since its inception. This blog introduces new tricks for the standard drivers that we added in version 4.6.0 of R. Many of these first came to life in the now deprecated package RweaveExtra.
Motivation
The standard Sweave drivers of package utils used to enforce some conventions that, if sensible in a statistical analysis context, could prove limiting in more general uses of literate programming. We will discuss the following conventions in the sequel:
- The values of chunk options have to be given explicitly, with no possibility to reuse values computed in earlier code chunks.
- Expressions in code chunks are always parsed on weaving, whether
the option
evalisTRUEorFALSE. - Code chunks with
eval=FALSEare automatically and irrevocably commented out on tangling. - Tangling allows to control whether evaluated code chunks are
extracted to a single file or to separate files via the option
split, but not whether or not a given code chunk is extracted in the first place (the optiondrop.evalFALSE=TRUEonly omits unevaluated chunks). - Code chunks collected in the same file on tangling end up always separated by two blank lines, and these are also present at the end of the file.
- The names of tangled files always end with a
.Rextension (omitting the other supported default of.Sthat is probably not much used nowadays).
The first item above limits the amount of logic one can build into a literate programming document. The combination of the second and third items makes it impossible to include in a code chunk invalid R code that should appear uncommented in the tangled script. The fourth and fifth items illustrate that authors have little control over what is extracted on tangling, and in what format. Finally, the sixth item becomes a limitation once the parsing requirement of item 2 is lifted and code chunks may contain code in languages other than R entirely.
Objects as chunk options
In Sweave, code chunks start with <<options>>= at the beginning
of a line, where the optional options have the form key=value
and are separated by commas. All options must take a value that used
to be either logical, numerical, or character.
The RweaveLatex driver now allows the value of logical and numerical
chunk options (only) to be the name of an object defined in earlier,
evaluated code chunks. This is useful to pass computed values to
options. The example below uses this feature to evaluate a chunk only
if a package is available, and to create a plot with computed
dimensions (this example and all others in the sequel are stripped of
all non-essential text content).
<<>>=
hasfoo <- requireNamespace("foo", quietly = TRUE)
ht <- 5
wd <- (1 + sqrt(5))/2 * ht
@
<<eval=hasfoo>>=
foo::foo(42)
@
<<echo=FALSE, fig=TRUE, width=wd, height=ht>>=
data(airquality, package="datasets")
library("graphics")
boxplot(Ozone ~ Month, data = airquality)
@
This type of option evaluation allows Sweave package vignettes to
conveniently deal with the situation that suggested dependencies (that
is: mentioned in the Suggests field of the DESCRIPTION file) are
used conditionally when checking.
Why limit the feature to logical and numerical options? All the values
of options first reach the Sweave driver as character strings. The
driver then tries to coerce them to the expected type. When that
fails, it is fairly safe for logical and numerical options to let the
driver check if an object of the correct mode exists in the
environment, and use its value when that is the case. Trying to do the
same for character options would prove impossible, or at least very
risky, since the driver could not tell apart intended values of
options from object names. Given that the vast majority of the options
of the RweaveLatex driver are either logical or numerical, we did
not deem necessary to devise a scheme to extend the feature to
character options.
Unparsable code in code chunks
Automatic parsing of code chunks is a nifty Sweave feature: it ensures that all code in a document is at least syntactically valid. However, sometimes unparsable code is a feature, not a bug. For example, an instructor may wish to include erroneous R code in a chunk for didactic purposes, yet distribute the tangled code uncommented such that learners may nevertheless evaluate it.
To cope with such situations, the RweaveLatex driver has gained
options to completely ignore code chunks on weaving. Chunks using
ignore.on.weave=TRUE are not parsed on weaving, yet remain written
out verbatim on tangling. A shorter alias weave for
!ignore.on.weave is also supported. An option ignore sets at once
ignore.on.weave and the equivalent option for the Rtangle driver
(see below).
The following example illustrates the use of the new option to maintain a document that evaluates without error on weaving, yet produces an uncommented script on tangling.
<<>>=
## Some R comparison operators.
486 < 521 # smaller than
486 >= 521 # greater or equal
486 != 521 # different from
@
<<ignore.on.weave=TRUE>>=
## Watch out! Equality is not tested with '='.
5 = 2 # syntax error
@
<<>>=
## The equality comparison operator is '=='.
486 == 486 # equality
@
We will cover another interesting use of this option in the following section.
Finer control of the tangling process
New options for the Rtangle driver address the last four motivations
mentioned earlier, thereby giving users finer control on the tangling
process.
First, Rtangle also gains an option ignore.on.tangle to completely
omit a code chunk on tangling, irrespective of the value of option
eval. The driver also supports the shorter alias tangle for
!ignore.on.tangle, and the option ignore to set both
ignore.on.weave and ignore.on.tangle at once. The possibility to
skip a chunk on tangling is convenient to avoid cluttering the tangled
script with unimportant code or, when using split=TRUE, the file
system with unneeded scripts.
Second, the new option chunk.sep allows to specify the separator
between code chunks when they are collected in a single file. The
historic default of two blank lines is maintained. One may also use
chunk.sep=FALSE to completely omit the separator, either throughout
a script when used as an argument to the Rtangle call, or between
two chunks when used as a chunk option. It is also worth noting that
the chunk separator is now added before all chunks, except the first
one. Therefore, there is no longer a chunk separator at the end of
tangled scripts.
Finally, Rtangle now provides an option extension to specify the
extension, without the leading dot, for the file name of a tangled
code chunk when splitting is selected. If used as extension=TRUE,
the default extension (usually R) is used. If the option is FALSE,
no extension is added to the file name. The option extension is
specially useful with ignore.on.weave=TRUE of RweaveLatex to
include code or text that the engine would not be able to parse on
weaving, yet that needs to be tangled to a file with a specific
extension, such as .sh or .txt.
Consider a file example.Rnw that contains the following code chunks:
<<script, echo=TRUE>>=
1:10
@
<<echo=FALSE, ignore.on.tangle=TRUE>>=
x <- 42
@
<<script>>=
gamma(1:5) # gamma function
factorial(0:4) # factorial
@
<<script, ignore.on.weave=TRUE, chunk.sep=FALSE>>=
?gamma # all related functions
@
<<hello, extension=sh, ignore.on.weave=TRUE>>=
#!/bin/sh
echo "Hello, World!"
exit 0
@
<<README, extension=FALSE, ignore.on.weave=TRUE>>=
Hello, World!
@
We use ignore.on.weave=TRUE in the fourth chunk to hide the help
system call from the weaving process. Now, tangling the file with the
following expression will create three files: script.R, hello.sh,
and README.
Stangle("example.Rnw", split = TRUE, prefix = FALSE,
annotate = FALSE, chunk.sep = "\n")
The file script.R is shown below. Notice that: the expression x <- 42 is omitted from the script; the help system call is present and
uncommented; the two main blocks of code are separated by a single
blank line; what were originally two code chunks became only one
block; there are no blank lines at the end of the file.
--- script.R ---
1:10
gamma(1:5) # gamma function
factorial(0:4) # factorial
?gamma # all related functions
As for hello.sh and README, shown below, the fact that the files
exist with an extension other than .R, and that their contents is
not R code, illustrates how one may use the options extension and
ignore.on.weave to maintain various pieces of code or text inside a
single source file.
--- hello.sh ---
#!/bin/sh
echo "Hello, World!"
exit 0
--- README ---
Hello, World!
Obtaining the name of the processed file
Weaving and tangling are often seen as separate, somewhat orthogonal procedures. However, one may devise various schemes where both weaving and tangling are needed to create a document. Here are a few examples:
- maintaining documentation and code together even though the code is not directly used to create the text;
- code that needs to be included verbatim in a document, but not necessarily as or where it appears in the sources;
- pieces of code that rely on other pieces being saved as files.
Vincent (the first author) proposes in a TUGboat article
a nice strategy to achieve this: embedding a tangling procedure
inside a weaving procedure by calling Stangle in a code chunk.
Now, this requires the name of the file to process… which is the
file being processed. To avoid hard coding the file name in the
Stangle call, we also introduced in the Sweave framework the
function SweaveGetSourceName that returns the name of the file being
woven by an Sweave process launched from the command line. This
allows to start Stangle inside Sweave with the a code chunk such as:
<<echo=FALSE, results=hide, ignore.on.tangle=TRUE>>=
FILE <- SweaveGetSourceName()
Stangle(FILE)
@
The function works by parsing the output of commandArgs, and
supports weaving processes started by means of either R CMD Sweave,
R -e, or Rscript -e.