I recently attended Henrik Bengtsson’s excellent UseR! 2026 workshop on the {futureverse}. Here are a few examples to show what it does.
library(future)
library(future.mirai)
library(tictoc)
library(tidyverse)
I’ll use this to create a slow running process.
rnorm_mean <- function(n, mean = 0, sd = 1) {
rnorm(n, mean, sd) |> mean()
}
First go sequential:
set.seed(42)
res_list <- list()
tic()
res_list$big1 <- rnorm_mean(2^27)
res_list$big2 <- rnorm_mean(2^27)
res_list$little <- rnorm_mean(20)
toc()
9.95 sec elapsed
Here are the means:
res_list
$big1
[1] 7.712652e-07
$big2
[1] 2.779673e-05
$little
[1] 0.005492432
Now same again concurrently. I’ve added in a “huge” variant to demo something…
plan(mirai_multisession, workers = 5)
set.seed(42)
future_list <- list()
tic()
future_list$big1 <- rnorm_mean(2^27) |> future(seed = TRUE)
future_list$big2 <- rnorm_mean(2^27) |> future(seed = TRUE)
future_list$huge <- rnorm_mean(2^28) |> future(seed = TRUE)
future_list$little <- rnorm_mean(20) |> future(seed = TRUE)
toc()
0.11 sec elapsed
Those have zoomed off on concurrent processes so R is still able to run further commands.
6 * 7
[1] 42
Check the value of the “little” one. This would block until it’s done but it should have been swift, despite being run last.
tic()
future_list$little |> value()
[1] -0.03712671
toc()
0.19 sec elapsed
Now take a look at the two “big” ones – this will block until they’re done:
tic()
future_list[c("big1", "big2")] |> map(value)
$big1
[1] 5.73721e-06
$big2
[1] 1.912083e-05
toc()
8.02 sec elapsed
Take a look to see whether “huge” is done yet (and check the other completed ones, for completeness).
future_list |> map(resolved)
$big1
[1] TRUE
$big2
[1] TRUE
$huge
[1] FALSE
$little
[1] TRUE
Can’t really be arsed waiting, so cancel it:
cancel(future_list$huge)
Check out its status:
future_list$huge
MiraiMultisessionFuture:
Label: <unnamed-3>
Expression:
rnorm_mean(2^28)
Globals: 1 objects totaling 2.83 KiB (function ‘rnorm_mean’ of 2.52 KiB)
Packages: 1 packages (‘stats’)
L'Ecuyer-CMRG RNG seed: c(10407, -1930318427, 797483284, 260641878, -1473084223, 1251695862, 1671345826)
Capture standard output: TRUE
Capture condition classes: ‘condition’ (excluding ‘<none>’)
Immediate condition classes: ‘immediateCondition’
Lazy evaluation: FALSE
Local evaluation: TRUE
Early signaling: FALSE
Actions: [n=3] ‘run’, ‘cancel’, ‘interrupt’
State: ‘canceled’ ("Future was canceled and interrupted")
Resolved: TRUE
Unique identifier: 3cc219d778a7ad023d990bb21d8b199e-3
Owner process: 3cc219d778a7ad023d990bb21d8b199e
Class: ‘MiraiMultisessionFuture’, ‘MiraiFuture’, ‘MultiprocessFuture’, ‘Future’
Value: <not collected>
Conditions captured: <none>
The real power of {future} is how it automagically futurizes calls like maps and bootstraps, hiding the future and value calls, but I found these examples handy to get to grips with what it’s doing.