Futures will relay output produced by functions such as
cat()
, print()
and str()
. More
specifically, output sent to the standard output (stdout) while a future
is evaluated will be captured and re-outputted (“relayed”) when the
value of the future is queried. Messages produced by
message()
, which formally are R conditions are also
captured and resignaled (“relayed”) as messages in the main R session.
Importantly, this works identically regardless of future backend
used.
For simplicitly, lets start with an illustration on how standard output (“stdout”) is captured and relayed:
> library(future)
> plan(multisession)
> fa <- future({ cat("Hello world!\n"); print(1:3); 42L })
> fb <- future({ str(iris); summary(iris) })
> a <- value(fa)
Hello world!
[1] 1 2 3
> b <- value(fb)
'data.frame': 150 obs. of 5 variables:
$ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
$ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
$ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
$ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
$ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
> a
[1] 42
> b
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100 setosa :50
1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300 versicolor:50
Median :5.800 Median :3.000 Median :4.350 Median :1.300 virginica :50
Mean :5.843 Mean :3.057 Mean :3.758 Mean :1.199
3rd Qu.:6.400 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800
Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500
Note that the captured standard output (stdout) will be relayed
each time value()
is called, e.g.
Output is relayed the same way when using future assignments
(%<-%
). For example,
> library(future)
> plan(multisession)
> a %<-% { cat("Hello world!\n"); print(1:3); 42L }
> b %<-% { str(iris); summary(iris) }
> a
Hello world!
[1] 1 2 3
[1] 42
> b
'data.frame': 150 obs. of 5 variables:
$ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
$ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
$ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
$ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
$ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100 setosa :50
1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300 versicolor:50
Median :5.800 Median :3.000 Median :4.350 Median :1.300 virginica :50
Mean :5.843 Mean :3.057 Mean :3.758 Mean :1.199
3rd Qu.:6.400 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800
Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500
Note how the captured output is relayed followed by the printing of
the value. Also, since the future value is only queried once when using
future assignments, or more specifically when using promises, the output
is only relayed once. For example, querying a
again will
only print its value, because it is now a regular R object:
Next, lets see what happens if we use message()
to
produce output:
> library(future)
> plan(multisession)
> fa <- future({ message("Hello world!"); 42L })
> value(fa)
Hello world!
[1] 42
Note that contrary to the captured stdout, which is captured as one single block output, messages are conditions that are captured separately. Unfortunately, it is not possible to preserve the ordering of interweaved stdout and message output. When using futures, stdout output will always be relayed first followed by each of the individual conditions captured. For example,
> library(future)
> plan(multisession)
> fa <- future({ message("Hello"); print(1:3); message("world!"); cat("ping\n"); 42L })
> value(fa)
[1] 1 2 3 ## <= stdout as a single ...
ping ## <= ... block of output
Hello ## <= 1st message
world! ## <= 2nd message
[1] 42
The output is relayed automatically also when using frontends such as future.apply or foreach with doFuture. Again, it works with any future backend. For example,
> library(future.apply)
> plan(future.callr::callr)
> y <- future_lapply(1:3, FUN = function(x) { cat("x =", x, "\n"); message("x : ", x); sqrt(x) })
x = 1
x = 2
x = 3
x : 1 ## <= 1st message
x : 2 ## <= 2nd message
x : 3 ## <= 3rd message
> str(y)
List of 3
$ : num 1
$ : num 1.41
$ : num 1.73
Equivalently,
To capture the output produced by futures, use
capture.output()
as you would do when capturing output
elsewhere in R. For example,
It is only the standard output that is relayed. It is not
possible to relay output send to the standard error (stderr),
e.g. output by cat(..., file = stderr())
will be lost. This
is due to a limitation
in R, preventing us from capturing stderr in a reliable way,
particularity across all backends. However, note that the captured
messages by message()
are outputted to stderr (as expected)
when resignaled/relayed.