From 58a71108e4221045f1f01b79341890f922f9b9cb Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 20:24:46 +0200
Subject: [PATCH 01/34] Docs: update parameter docs for `sapply` operation
---
R/AsyncBackend.R | 3 +--
R/Context.R | 3 +--
R/ProgressTrackingContext.R | 3 +--
R/Service.R | 3 +--
R/SyncBackend.R | 3 +--
5 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/R/AsyncBackend.R b/R/AsyncBackend.R
index ee70b59..f8687a0 100644
--- a/R/AsyncBackend.R
+++ b/R/AsyncBackend.R
@@ -426,8 +426,7 @@ AsyncBackend <- R6::R6Class("AsyncBackend",
#' @description
#' Run a task on the backend akin to [parallel::parSapply()].
#'
- #' @param x A vector (i.e., usually of integers) to pass to the `fun`
- #' function.
+ #' @param x An atomic vector or list to pass to the `fun` function.
#'
#' @param fun A function to apply to each element of `x`.
#'
diff --git a/R/Context.R b/R/Context.R
index e22f5a3..2546932 100644
--- a/R/Context.R
+++ b/R/Context.R
@@ -185,8 +185,7 @@ Context <- R6::R6Class("Context",
#' @description
#' Run a task on the backend akin to [parallel::parSapply()].
#'
- #' @param x A vector (i.e., usually of integers) to pass to the `fun`
- #' function.
+ #' @param x An atomic vector or list to pass to the `fun` function.
#'
#' @param fun A function to apply to each element of `x`.
#'
diff --git a/R/ProgressTrackingContext.R b/R/ProgressTrackingContext.R
index a409d9f..f425da4 100644
--- a/R/ProgressTrackingContext.R
+++ b/R/ProgressTrackingContext.R
@@ -265,8 +265,7 @@ ProgressTrackingContext <- R6::R6Class("ProgressTrackingContext",
#' Run a task on the backend akin to [parallel::parSapply()], but with a
#' progress bar.
#'
- #' @param x A vector (i.e., usually of integers) to pass to the `fun`
- #' function.
+ #' @param x An atomic vector or list to pass to the `fun` function.
#'
#' @param fun A function to apply to each element of `x`.
#'
diff --git a/R/Service.R b/R/Service.R
index 4a55c99..b346059 100644
--- a/R/Service.R
+++ b/R/Service.R
@@ -101,8 +101,7 @@ Service <- R6::R6Class("Service",
#' @description
#' Run a task on the backend akin to [parallel::parSapply()].
#'
- #' @param x A vector (i.e., usually of integers) to pass to the `fun`
- #' function.
+ #' @param x An atomic vector or list to pass to the `fun` function.
#'
#' @param fun A function to apply to each element of `x`.
#'
diff --git a/R/SyncBackend.R b/R/SyncBackend.R
index e16415d..d6dffd4 100644
--- a/R/SyncBackend.R
+++ b/R/SyncBackend.R
@@ -273,8 +273,7 @@ SyncBackend <- R6::R6Class("SyncBackend",
#' @description
#' Run a task on the backend akin to [parallel::parSapply()].
#'
- #' @param x A vector (i.e., usually of integers) to pass to the `fun`
- #' function.
+ #' @param x An atomic vector or list to pass to the `fun` function.
#'
#' @param fun A function to apply to each element of `x`.
#'
From 74664d79110c7556e18d4ca259f424ba05ec6c1e Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 20:29:02 +0200
Subject: [PATCH 02/34] Docs: update docs for `get_output` method
---
R/AsyncBackend.R | 6 ++++--
R/Context.R | 6 ++++--
R/Service.R | 12 +++++++-----
R/SyncBackend.R | 6 ++++--
4 files changed, 19 insertions(+), 11 deletions(-)
diff --git a/R/AsyncBackend.R b/R/AsyncBackend.R
index f8687a0..f6cc8d0 100644
--- a/R/AsyncBackend.R
+++ b/R/AsyncBackend.R
@@ -465,8 +465,10 @@ AsyncBackend <- R6::R6Class("AsyncBackend",
#' task is still running.
#'
#' @return
- #' A vector or list of the same length as `x` containing the results of
- #' the `fun`. It resembles the format of [base::sapply()].
+ #' A vector, matrix, or list of the same length as `x`, containing the
+ #' results of the `fun`. The output format differs based on the specific
+ #' operation employed. Check out the documentation for the `apply`
+ #' operations of [`parallel::parallel`] for more information.
get_output = function(wait = FALSE) {
# Reset the output on exit.
on.exit({
diff --git a/R/Context.R b/R/Context.R
index 2546932..e1a89db 100644
--- a/R/Context.R
+++ b/R/Context.R
@@ -216,8 +216,10 @@ Context <- R6::R6Class("Context",
#' task.
#'
#' @return
- #' A vector or list of the same length as `x` containing the results of
- #' the `fun`. It resembles the format of [base::sapply()].
+ #' A vector, matrix, or list of the same length as `x`, containing the
+ #' results of the `fun`. The output format differs based on the specific
+ #' operation employed. Check out the documentation for the `apply`
+ #' operations of [`parallel::parallel`] for more information.
get_output = function(...) {
# Consume the backend API.
private$.backend$get_output(...)
diff --git a/R/Service.R b/R/Service.R
index b346059..a2cfe8a 100644
--- a/R/Service.R
+++ b/R/Service.R
@@ -118,6 +118,9 @@ Service <- R6::R6Class("Service",
#' @description
#' Get the output of the task execution.
#'
+ #' @param ... Additional optional arguments that may be used by concrete
+ #' implementations.
+ #'
#' @details
#' This method fetches the output of the task execution after calling
#' the `sapply()` method. It returns the output and immediately removes
@@ -125,12 +128,11 @@ Service <- R6::R6Class("Service",
#' not advised. This method should be called after the execution of a
#' task.
#'
- #' @param ... Additional optional arguments that may be used by concrete
- #' implementations.
- #'
#' @return
- #' A vector or list of the same length as `x` containing the results of
- #' the `fun`. It resembles the format of [base::sapply()].
+ #' A vector, matrix, or list of the same length as `x`, containing the
+ #' results of the `fun`. The output format differs based on the specific
+ #' operation employed. Check out the documentation for the `apply`
+ #' operations of [`parallel::parallel`] for more information.
get_output = function(...) {
Exception$method_not_implemented()
}
diff --git a/R/SyncBackend.R b/R/SyncBackend.R
index d6dffd4..3ce2e31 100644
--- a/R/SyncBackend.R
+++ b/R/SyncBackend.R
@@ -298,8 +298,10 @@ SyncBackend <- R6::R6Class("SyncBackend",
#' task.
#'
#' @return
- #' A vector or list of the same length as `x` containing the results of
- #' the `fun`. It resembles the format of [base::sapply()].
+ #' A vector, matrix, or list of the same length as `x`, containing the
+ #' results of the `fun`. The output format differs based on the specific
+ #' operation employed. Check out the documentation for the `apply`
+ #' operations of [`parallel::parallel`] for more information.
get_output = function() {
# Reset the output on exit.
on.exit({
From c32e3c7c40d2a8c90e50aefe94ed029794964f45 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 20:43:48 +0200
Subject: [PATCH 03/34] Feat: add `lapply` method to `Service` interface
---
R/Service.R | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/R/Service.R b/R/Service.R
index a2cfe8a..1e180da 100644
--- a/R/Service.R
+++ b/R/Service.R
@@ -115,6 +115,23 @@ Service <- R6::R6Class("Service",
Exception$method_not_implemented()
},
+ #' @description
+ #' Run a task on the backend akin to [parallel::parLapply()].
+ #'
+ #' @param x An atomic vector or list to pass to the `fun` function.
+ #'
+ #' @param fun A function to apply to each element of `x`.
+ #'
+ #' @param ... Additional arguments to pass to the `fun` function.
+ #'
+ #' @return
+ #' This method returns void. The output of the task execution must be
+ #' stored in the private field `.output` on the [`parabar::Backend`]
+ #' abstract class, and is accessible via the `get_output()` method.
+ lapply = function(x, fun, ...) {
+ Exception$method_not_implemented()
+ },
+
#' @description
#' Get the output of the task execution.
#'
From 6bcba63b9ab9a4ef21459e2e6935ae7ac1c22ff0 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 20:45:13 +0200
Subject: [PATCH 04/34] Feat: add `lapply` implementation for `SyncBackend`
---
R/SyncBackend.R | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/R/SyncBackend.R b/R/SyncBackend.R
index 3ce2e31..6b67518 100644
--- a/R/SyncBackend.R
+++ b/R/SyncBackend.R
@@ -157,6 +157,12 @@ SyncBackend <- R6::R6Class("SyncBackend",
parallel::parSapply(private$.cluster, X = x, FUN = fun, ...)
},
+ # A wrapper around `parallel:parLapply` to run tasks on the cluster.
+ .lapply = function(x, fun, ...) {
+ # Run the task and return the results.
+ parallel::parLapply(private$.cluster, X = x, fun = fun, ...)
+ },
+
# Clear the current output on the backend.
.clear_output = function() {
# Clear output.
@@ -287,6 +293,23 @@ SyncBackend <- R6::R6Class("SyncBackend",
private$.output = private$.sapply(x, fun, ...)
},
+ #' @description
+ #' Run a task on the backend akin to [parallel::parLapply()].
+ #'
+ #' @param x An atomic vector or list to pass to the `fun` function.
+ #'
+ #' @param fun A function to apply to each element of `x`.
+ #'
+ #' @param ... Additional arguments to pass to the `fun` function.
+ #'
+ #' @return
+ #' This method returns void. The output of the task execution must be
+ #' stored in the private field `.output` on the [`parabar::Backend`]
+ #' abstract class, and is accessible via the `get_output()` method.
+ lapply = function(x, fun, ...) {
+ private$.output = private$.lapply(x, fun, ...)
+ },
+
#' @description
#' Get the output of the task execution.
#'
From fd8874140e5547e0ee23d70b53c494ddce449af2 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 20:45:43 +0200
Subject: [PATCH 05/34] Feat: add `lapply` implementation for `AsyncBackend`
---
R/AsyncBackend.R | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/R/AsyncBackend.R b/R/AsyncBackend.R
index f6cc8d0..0a534f1 100644
--- a/R/AsyncBackend.R
+++ b/R/AsyncBackend.R
@@ -225,6 +225,21 @@ AsyncBackend <- R6::R6Class("AsyncBackend",
}, args = list(x, fun, dots))
},
+ # Run tasks on the cluster in the session via `parallel:parLapply` asynchronously.
+ .lapply = function(x, fun, ...) {
+ # Capture the `...`.
+ dots <- list(...)
+
+ # Perform the evaluation from the `R` session.
+ private$.cluster$call(function(x, fun, dots) {
+ # Run the task.
+ output <- do.call(parallel::parLapply, c(list(cluster, x, fun), dots))
+
+ # Return to the session.
+ return(output)
+ }, args = list(x, fun, dots))
+ },
+
# Clear the current output on the backend.
.clear_output = function() {
# Clear output.
@@ -444,6 +459,27 @@ AsyncBackend <- R6::R6Class("AsyncBackend",
private$.sapply(x, fun, ...)
},
+ #' @description
+ #' Run a task on the backend akin to [parallel::parLapply()].
+ #'
+ #' @param x An atomic vector or list to pass to the `fun` function.
+ #'
+ #' @param fun A function to apply to each element of `x`.
+ #'
+ #' @param ... Additional arguments to pass to the `fun` function.
+ #'
+ #' @return
+ #' This method returns void. The output of the task execution must be
+ #' stored in the private field `.output` on the [`parabar::Backend`]
+ #' abstract class, and is accessible via the `get_output()` method.
+ lapply = function(x, fun, ...) {
+ # Throw if backend is busy.
+ private$.throw_if_backend_is_busy()
+
+ # Deploy the task asynchronously.
+ private$.lapply(x, fun, ...)
+ },
+
#' @description
#' Get the output of the task execution.
#'
From 8822d8153eea7cf0c85d631739c3df97798a44f4 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 20:45:56 +0200
Subject: [PATCH 06/34] Feat: add `lapply` implementation for `Context`
---
R/Context.R | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/R/Context.R b/R/Context.R
index e1a89db..82e31c2 100644
--- a/R/Context.R
+++ b/R/Context.R
@@ -200,6 +200,24 @@ Context <- R6::R6Class("Context",
private$.backend$sapply(x = x, fun = fun, ...)
},
+ #' @description
+ #' Run a task on the backend akin to [parallel::parLapply()].
+ #'
+ #' @param x An atomic vector or list to pass to the `fun` function.
+ #'
+ #' @param fun A function to apply to each element of `x`.
+ #'
+ #' @param ... Additional arguments to pass to the `fun` function.
+ #'
+ #' @return
+ #' This method returns void. The output of the task execution must be
+ #' stored in the private field `.output` on the [`parabar::Backend`]
+ #' abstract class, and is accessible via the `get_output()` method.
+ lapply = function(x, fun, ...) {
+ # Consume the backend API.
+ private$.backend$lapply(x = x, fun = fun, ...)
+ },
+
#' @description
#' Get the output of the task execution.
#'
From 89f5a4d32f4ffb814b64df800f1d0546bf8ebfe6 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 20:50:57 +0200
Subject: [PATCH 07/34] Feat: add `.execute` method for progress tracking steps
---
R/ProgressTrackingContext.R | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/R/ProgressTrackingContext.R b/R/ProgressTrackingContext.R
index f425da4..3bba92b 100644
--- a/R/ProgressTrackingContext.R
+++ b/R/ProgressTrackingContext.R
@@ -212,6 +212,27 @@ ProgressTrackingContext <- R6::R6Class("ProgressTrackingContext",
# Close and remove the progress bar.
private$.bar$terminate()
+ },
+
+ # Template function for tracking progress of backend operations.
+ .execute = function(operation, x, fun) {
+ # Create file for logging progress.
+ log <- private$.make_log()
+
+ # Clear the temporary file on function exit.
+ on.exit({
+ # Remove.
+ unlink(log)
+ })
+
+ # Decorate task function and save it as `task` (i.e., for readability).
+ task <- private$.decorate(task = fun, log = log)
+
+ # Substitute `fun` with `task` (i.e., for readability) and evaluate.
+ eval(substituteDirect(operation, list(fun = task)))
+
+ # Show the progress bar and block the main process.
+ private$.show_progress(total = length(x), log = log)
}
),
From 68277ea7cb9f97baf759333cba359f02eab58995 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 20:56:17 +0200
Subject: [PATCH 08/34] Refactor: update `sapply` to use `.execute` private
method
This is in the context of `ProgressTrackingContext` to generalize the progress
tracking to other backend operations (e.g., `lapply` and `apply`)
---
R/ProgressTrackingContext.R | 24 ++++++++----------------
1 file changed, 8 insertions(+), 16 deletions(-)
diff --git a/R/ProgressTrackingContext.R b/R/ProgressTrackingContext.R
index 3bba92b..4271cc9 100644
--- a/R/ProgressTrackingContext.R
+++ b/R/ProgressTrackingContext.R
@@ -297,23 +297,15 @@ ProgressTrackingContext <- R6::R6Class("ProgressTrackingContext",
#' stored in the private field `.output` on the [`parabar::Backend`]
#' abstract class, and is accessible via the `get_output()` method.
sapply = function(x, fun, ...) {
- # Create file for logging progress.
- log <- private$.make_log()
-
- # Clear the temporary file on function exit.
- on.exit({
- # Remove.
- unlink(log)
- })
-
- # Decorate task function.
- task <- private$.decorate(task = fun, log = log)
-
- # Execute the decorated task.
- super$sapply(x = x, fun = task, ...)
+ # Prepare the backend operation with early evaluated `...`.
+ operation <- bquote(
+ do.call(
+ super$sapply, c(list(x = x, fun = fun), .(list(...)))
+ )
+ )
- # Show the progress bar and block the main process.
- private$.show_progress(total = length(x), log = log)
+ # Execute the task using the desired backend operation.
+ private$.execute(operation = operation, x = x, fun = fun)
}
),
From 50e7dbc2e1bc20d2066bcd8ee690fef6ed7eceb8 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 20:57:11 +0200
Subject: [PATCH 09/34] Feat: add `lapply` implementation for
`ProgressTrackingContext`
---
R/ProgressTrackingContext.R | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/R/ProgressTrackingContext.R b/R/ProgressTrackingContext.R
index 4271cc9..6ba5450 100644
--- a/R/ProgressTrackingContext.R
+++ b/R/ProgressTrackingContext.R
@@ -306,6 +306,32 @@ ProgressTrackingContext <- R6::R6Class("ProgressTrackingContext",
# Execute the task using the desired backend operation.
private$.execute(operation = operation, x = x, fun = fun)
+ },
+
+ #' @description
+ #' Run a task on the backend akin to [parallel::parLapply()], but with a
+ #' progress bar.
+ #'
+ #' @param x An atomic vector or list to pass to the `fun` function.
+ #'
+ #' @param fun A function to apply to each element of `x`.
+ #'
+ #' @param ... Additional arguments to pass to the `fun` function.
+ #'
+ #' @return
+ #' This method returns void. The output of the task execution must be
+ #' stored in the private field `.output` on the [`parabar::Backend`]
+ #' abstract class, and is accessible via the `get_output()` method.
+ lapply = function(x, fun, ...) {
+ # Prepare the backend operation with early evaluated `...`.
+ operation <- bquote(
+ do.call(
+ super$lapply, c(list(x = x, fun = fun), .(list(...)))
+ )
+ )
+
+ # Execute the task via the `lapply` backend operation.
+ private$.execute(operation = operation, x = x, fun = fun)
}
),
From ce9b15af0a7b59697e30b58e67382f82d6df1414 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 20:59:48 +0200
Subject: [PATCH 10/34] Docs: update comments in `AsyncBackend`
---
R/AsyncBackend.R | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/R/AsyncBackend.R b/R/AsyncBackend.R
index 0a534f1..71933a5 100644
--- a/R/AsyncBackend.R
+++ b/R/AsyncBackend.R
@@ -210,7 +210,7 @@ AsyncBackend <- R6::R6Class("AsyncBackend",
}, args = list(capture))
},
- # Run tasks on the cluster in the session asynchronously.
+ # Run tasks asynchronously via the cluster in the session.
.sapply = function(x, fun, ...) {
# Capture the `...`.
dots <- list(...)
@@ -225,7 +225,7 @@ AsyncBackend <- R6::R6Class("AsyncBackend",
}, args = list(x, fun, dots))
},
- # Run tasks on the cluster in the session via `parallel:parLapply` asynchronously.
+ # Run tasks asynchronously via the cluster in the session.
.lapply = function(x, fun, ...) {
# Capture the `...`.
dots <- list(...)
From 3183e83cc52afdcf8e84bb490397be0246e81db7 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Sun, 30 Apr 2023 21:12:54 +0200
Subject: [PATCH 11/34] Refactor: simplify `.execute` in
`ProgressTrackingContext`
---
R/ProgressTrackingContext.R | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/R/ProgressTrackingContext.R b/R/ProgressTrackingContext.R
index 6ba5450..9096759 100644
--- a/R/ProgressTrackingContext.R
+++ b/R/ProgressTrackingContext.R
@@ -225,11 +225,11 @@ ProgressTrackingContext <- R6::R6Class("ProgressTrackingContext",
unlink(log)
})
- # Decorate task function and save it as `task` (i.e., for readability).
- task <- private$.decorate(task = fun, log = log)
+ # Decorate the task function.
+ fun <- private$.decorate(task = fun, log = log)
- # Substitute `fun` with `task` (i.e., for readability) and evaluate.
- eval(substituteDirect(operation, list(fun = task)))
+ # Evaluate the operation now referencing the decorated task.
+ eval(operation)
# Show the progress bar and block the main process.
private$.show_progress(total = length(x), log = log)
From c2b5184187adbc30dade065f138f65a5145cba65 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:44:14 +0200
Subject: [PATCH 12/34] Feat: add `UserApiConsumer` class for the user `API`
---
R/UserApiConsumer.R | 233 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 233 insertions(+)
create mode 100644 R/UserApiConsumer.R
diff --git a/R/UserApiConsumer.R b/R/UserApiConsumer.R
new file mode 100644
index 0000000..39b86c5
--- /dev/null
+++ b/R/UserApiConsumer.R
@@ -0,0 +1,233 @@
+#' @include Helper.R Warning.R ContextFactory.R BarFactory.R
+
+#' @title
+#' UserApiConsumer
+#'
+#' @description
+#' This class is an opinionated interface around the developer API of the
+#' [`parabar::parabar`] package. See the **Details** section for more
+#' information on how this class works.
+#'
+#' @param ... Additional arguments to pass to the `fun` function.
+#'
+#' @details
+#' This class acts as a wrapper around the [`R6::R6`] developer API of the
+#' [`parabar::parabar`] package. In a nutshell, it provides an opinionated
+#' interface by wrapping the developer API in simple functional calls. More
+#' specifically, for executing a task in parallel, this class performs the
+#' following steps:
+#' - Validates the backend provided.
+#' - Instantiates an appropriate [`parabar::parabar`] context based on the
+#' backend. If the backend supports progress tracking (i.e., the backend is an
+#' instance of [`parabar::AsyncBackend`]), a progress tracking context (i.e.,
+#' [`parabar::ProgressTrackingContext`]) is instantiated and used. Otherwise,
+#' a regular context (i.e., [`parabar::Context`]) is instantiated. A regular
+#' context is also used if the progress tracking is disabled via the
+#' [`parabar::Options`] instance.
+#' - Registers the [`backend`][`parabar::Backend`] with the context.
+#' - Instantiates and configures the progress bar based on the
+#' [`parabar::Options`] instance in the session [`base::.Options`] list.
+#' - Executes the task in parallel, and displays a progress bar if appropriate.
+#' - Fetches the results from the backend and returns them.
+#'
+#' @examples
+#' # Define a simple task.
+#' task <- function(x) {
+#' # Perform computations.
+#' Sys.sleep(0.01)
+#'
+#' # Return the result.
+#' return(x + 1)
+#' }
+#'
+#' # Start an asynchronous backend.
+#' backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async")
+#'
+#' # Change the progress bar options.
+#' configure_bar(type = "modern", format = "[:bar] :percent")
+#'
+#' # Create an user API consumer.
+#' consumer <- UserApiConsumer$new()
+#'
+#' # Execute the task using the `sapply` parallel operation.
+#' output_sapply <- consumer$sapply(backend = backend, x = 1:200, fun = task)
+#'
+#' # Print the head of the `sapply` operation output.
+#' head(output_sapply)
+#'
+#' # Execute the task using the `sapply` parallel operation.
+#' output_lapply <- consumer$lapply(backend = backend, x = 1:200, fun = task)
+#'
+#' # Print the head of the `lapply` operation output.
+#' head(output_lapply)
+#'
+#' # Stop the backend.
+#' stop_backend(backend)
+#'
+#' @seealso
+#' [parabar::start_backend()], [parabar::stop_backend()],
+#' [parabar::configure_bar()], [parabar::par_sapply()], and
+#' [parabar::par_lapply()].
+#'
+#' @export
+UserApiConsumer <- R6::R6Class("UserApiConsumer",
+ private = list(
+ # Execute a task via the user API with the corresponding operation.
+ .execute = function(backend, parallel_operation, sequential_operation) {
+ # If no backend is provided.
+ if (is.null(backend)) {
+ # Then use the non-parallel (i.e., sequential) operation.
+ output <- eval(sequential_operation)
+
+ # Return results.
+ return(output)
+
+ # Otherwise, if a backend is provided.
+ } else {
+ # Check the type.
+ Helper$check_object_type(backend, "Backend")
+ }
+
+ # Get user warning settings.
+ user_options <- options()
+
+ # Enable printing warnings as soon as they occur.
+ options(warn = 1)
+
+ # Restore user's original settings.
+ on.exit({
+ # Reset user's options.
+ options(user_options)
+ })
+
+ # Whether to track progress or not.
+ progress <- get_option("progress_track")
+
+ # If the user requested progress tracking and the backend does not support it.
+ if (progress && !backend$supports_progress) {
+ # Warn the users.
+ Warning$progress_not_supported_for_backend(backend)
+ }
+
+ # Create a context manager factory.
+ context_factory <- ContextFactory$new()
+
+ # If progress is requested and the conditions are right.
+ if (progress && backend$supports_progress && interactive()) {
+ # Then use a progress-decorated context.
+ context <- context_factory$get("progress")
+
+ # Progress bar type.
+ bar_type <- get_option("progress_bar_type")
+
+ # Progress bar default configuration.
+ bar_config <- get_option("progress_bar_config")[[bar_type]]
+
+ # Create a bar factory.
+ bar_factory <- BarFactory$new()
+
+ # Get a bar of desired type.
+ bar <- bar_factory$get(bar_type)
+
+ # Set the bar.
+ context$set_bar(bar)
+
+ # Configure the bar.
+ do.call(context$configure_bar, bar_config)
+
+ # Otherwise, if progress tracking is not requested, nor possible.
+ } else {
+ # Use a regular context.
+ context <- context_factory$get("regular")
+ }
+
+ # Register the backend with the context.
+ context$set_backend(backend)
+
+ # Register the backend with the context.
+ context$set_backend(backend)
+
+ # Execute the task via the requested parallel operation.
+ eval(parallel_operation)
+
+ # If the current context wraps a backend that supports progress tracking.
+ if (context$backend$supports_progress) {
+ # Then wait for the results.
+ output <- context$get_output(wait = TRUE)
+ } else {
+ # Otherwise, return the output whenever the task is finished.
+ output <- context$get_output()
+ }
+
+ return(output)
+ }
+ ),
+
+ public = list(
+ #' @description
+ #' Execute a task in parallel akin to [parallel::parSapply()].
+ #'
+ #' @param backend An object of class [`parabar::Backend`] as returned by
+ #' the [parabar::start_backend()] function. It can also be `NULL` to run
+ #' the task sequentially via [base::sapply()].
+ #'
+ #' @param x An atomic vector or list to pass to the `fun` function.
+ #'
+ #' @param fun A function to apply to each element of `x`.
+ #'
+ #' @return
+ #' A vector of the same length as `x` containing the results of the
+ #' `fun`. The output format resembles that of [base::sapply()].
+ sapply = function(backend, x, fun, ...) {
+ # Prepare the sequential operation.
+ sequential <- bquote(
+ do.call(
+ base::sapply, c(list(X = .(x), FUN = .(fun)), .(list(...)))
+ )
+ )
+
+ # Prepare the parallel operation.
+ parallel <- bquote(
+ do.call(
+ context$sapply, c(list(x = .(x), fun = .(fun)), .(list(...)))
+ )
+ )
+
+ # Execute the `sapply` operation accordingly and return the results.
+ private$.execute(backend, parallel, sequential)
+ },
+
+ #' @description
+ #' Execute a task in parallel akin to [parallel::parLapply()].
+ #'
+ #' @param backend An object of class [`parabar::Backend`] as returned by
+ #' the [parabar::start_backend()] function. It can also be `NULL` to run
+ #' the task sequentially via [base::lapply()].
+ #'
+ #' @param x An atomic vector or list to pass to the `fun` function.
+ #'
+ #' @param fun A function to apply to each element of `x`.
+ #'
+ #' @return
+ #' A list of the same length as `x` containing the results of the `fun`.
+ #' The output format resembles that of [base::lapply()].
+ lapply = function(backend, x, fun, ...) {
+ # Prepare the sequential operation.
+ sequential <- bquote(
+ do.call(
+ base::lapply, c(list(X = .(x), FUN = .(fun)), .(list(...)))
+ )
+ )
+
+ # Prepare the parallel operation.
+ parallel <- bquote(
+ do.call(
+ context$lapply, c(list(x = .(x), fun = .(fun)), .(list(...)))
+ )
+ )
+
+ # Execute the `lapply` operation accordingly and return the results.
+ private$.execute(backend, parallel, sequential)
+ }
+ )
+)
From ad21b1b48dcd2377cb2214dfa9301d22fb34c88c Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:46:23 +0200
Subject: [PATCH 13/34] Refactor: update `par_sapply` to use the consumer `API`
---
R/exports.r | 86 +++--------------------------------------------------
1 file changed, 4 insertions(+), 82 deletions(-)
diff --git a/R/exports.r b/R/exports.r
index b136d78..cbe75c2 100644
--- a/R/exports.r
+++ b/R/exports.r
@@ -158,87 +158,9 @@ evaluate <- function(backend, expression) {
#' @template par-sapply
#' @export
par_sapply <- function(backend = NULL, x, fun, ...) {
- # If no backend is provided.
- if (is.null(backend)) {
- # Then use the built in, non-parallel `base::sapply`.
- output <- base::sapply(X = x, FUN = fun, ...)
-
- # Return results.
- return(output)
-
- # Otherwise, if a backend is provided.
- } else {
- # Check the type.
- Helper$check_object_type(backend, "Backend")
- }
-
- # Get user warning settings.
- user_options <- options()
-
- # Enable printing warnings as soon as they occur.
- options(warn = 1)
-
- # Restore user's original settings.
- on.exit({
- # Reset user's options.
- options(user_options)
- })
-
- # Whether to track progress or not.
- progress <- get_option("progress_track")
-
- # If the user requested progress tracking and the backend does not support it.
- if (progress && !backend$supports_progress) {
- # Warn the users.
- Warning$progress_not_supported_for_backend(backend)
- }
-
- # Create a context manager factory.
- context_factory <- ContextFactory$new()
-
- # If progress is requested and the conditions are right.
- if (progress && backend$supports_progress && interactive()) {
- # Then use a progress-decorated context.
- context <- context_factory$get("progress")
-
- # Progress bar type.
- bar_type <- get_option("progress_bar_type")
-
- # Progress bar default configuration.
- bar_config <- get_option("progress_bar_config")[[bar_type]]
-
- # Create a bar factory.
- bar_factory <- BarFactory$new()
-
- # Get a bar of desired type.
- bar <- bar_factory$get(bar_type)
-
- # Set the bar.
- context$set_bar(bar)
-
- # Configure the bar.
- do.call(context$configure_bar, bar_config)
-
- # Otherwise, if progress tracking is not requested, nor possible.
- } else {
- # Use a regular context.
- context <- context_factory$get("regular")
- }
-
- # Register the backend with the context.
- context$set_backend(backend)
-
- # Execute the task using the backend provided (i.e., aka context).
- context$sapply(x = x, fun = fun, ...)
-
- # If the current context wraps a backend that supports progress tracking.
- if (context$backend$supports_progress) {
- # Then wait for the results.
- output <- context$get_output(wait = TRUE)
- } else {
- # Otherwise, return the output whenever the task is finished.
- output <- context$get_output()
- }
+ # Create an user API consumer.
+ consumer <- UserApiConsumer$new()
- return(output)
+ # Execute the task using the `sapply` parallel operation.
+ consumer$sapply(backend = backend, x = x, fun = fun, ...)
}
From cb37ada2f5c2900987c8d132be662a5c18e964b5 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:47:04 +0200
Subject: [PATCH 14/34] Feat: add `par_lapply` exported function
---
R/exports.r | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/R/exports.r b/R/exports.r
index cbe75c2..e8338d7 100644
--- a/R/exports.r
+++ b/R/exports.r
@@ -164,3 +164,13 @@ par_sapply <- function(backend = NULL, x, fun, ...) {
# Execute the task using the `sapply` parallel operation.
consumer$sapply(backend = backend, x = x, fun = fun, ...)
}
+
+#' @template par-lapply
+#' @export
+par_lapply <- function(backend = NULL, x, fun, ...) {
+ # Create an user API consumer.
+ consumer <- UserApiConsumer$new()
+
+ # Execute the task using the `sapply` parallel operation.
+ consumer$lapply(backend = backend, x = x, fun = fun, ...)
+}
From 305f386441cac150a4db2fb1b87ce07ced8c656b Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:47:59 +0200
Subject: [PATCH 15/34] Docs: add documentation for `par_lapply` function
---
man-roxygen/par-lapply.R | 89 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
create mode 100644 man-roxygen/par-lapply.R
diff --git a/man-roxygen/par-lapply.R b/man-roxygen/par-lapply.R
new file mode 100644
index 0000000..b103d14
--- /dev/null
+++ b/man-roxygen/par-lapply.R
@@ -0,0 +1,89 @@
+#' @title
+#' Run a Task in Parallel
+#'
+#' @description
+#' This function can be used to run a task in parallel. The task is executed in
+#' parallel on the specified backend, similar to [parallel::parLapply()]. If
+#' `backend = NULL`, the task is executed sequentially using [base::lapply()].
+#' See the **Details** section for more information on how this function works.
+#'
+#' @param backend An object of class [`parabar::Backend`] as returned by the
+#' [parabar::start_backend()] function. It can also be `NULL` to run the task
+#' sequentially via [base::lapply()]. The default value is `NULL`.
+#'
+#' @param x An atomic vector or list to pass to the `fun` function.
+#'
+#' @param fun A function to apply to each element of `x`.
+#'
+#' @param ... Additional arguments to pass to the `fun` function.
+#'
+#' @details
+#' This function uses the [`parabar::UserApiConsumer`] class that acts like an
+#' interface for the developer API of the [`parabar::parabar`] package.
+#'
+#' @return
+#' A list of the same length as `x` containing the results of the `fun`. The
+#' output format resembles that of [base::lapply()].
+#'
+#' @examples
+#' \donttest{
+#'
+#' # Define a simple task.
+#' task <- function(x) {
+#' # Perform computations.
+#' Sys.sleep(0.01)
+#'
+#' # Return the result.
+#' return(x + 1)
+#' }
+#'
+#' # Start an asynchronous backend.
+#' backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async")
+#'
+#' # Run a task in parallel.
+#' results <- par_lapply(backend, x = 1:300, fun = task)
+#'
+#' # Disable progress tracking.
+#' set_option("progress_track", FALSE)
+#'
+#' # Run a task in parallel.
+#' results <- par_lapply(backend, x = 1:300, fun = task)
+#'
+#' # Enable progress tracking.
+#' set_option("progress_track", TRUE)
+#'
+#' # Change the progress bar options.
+#' configure_bar(type = "modern", format = "[:bar] :percent")
+#'
+#' # Run a task in parallel.
+#' results <- par_lapply(backend, x = 1:300, fun = task)
+#'
+#' # Stop the backend.
+#' stop_backend(backend)
+#'
+#' # Start a synchronous backend.
+#' backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "sync")
+#'
+#' # Run a task in parallel.
+#' results <- par_lapply(backend, x = 1:300, fun = task)
+#'
+#' # Disable progress tracking to remove the warning that progress is not supported.
+#' set_option("progress_track", FALSE)
+#'
+#' # Run a task in parallel.
+#' results <- par_lapply(backend, x = 1:300, fun = task)
+#'
+#' # Stop the backend.
+#' stop_backend(backend)
+#'
+#' # Run the task using the `base::lapply` (i.e., non-parallel).
+#' results <- par_lapply(NULL, x = 1:300, fun = task)
+#'
+#' }
+#'
+#' @seealso
+#' [parabar::start_backend()], [parabar::peek()], [parabar::export()],
+#' [parabar::evaluate()], [parabar::clear()], [parabar::configure_bar()],
+#' [parabar::par_sapply()], [parabar::stop_backend()], [parabar::set_option()],
+#' [parabar::get_option()], [`parabar::Options`], [`parabar::UserApiConsumer`],
+#' and [`parabar::Service`].
From 85ae54a139c79dd0d215b60a9d5e61201bef8696 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:48:33 +0200
Subject: [PATCH 16/34] Docs: update documentation for `par_sapply` function
---
man-roxygen/par-sapply.R | 26 ++++++++------------------
1 file changed, 8 insertions(+), 18 deletions(-)
diff --git a/man-roxygen/par-sapply.R b/man-roxygen/par-sapply.R
index fa3d23c..9361a55 100644
--- a/man-roxygen/par-sapply.R
+++ b/man-roxygen/par-sapply.R
@@ -11,30 +11,19 @@
#' [parabar::start_backend()] function. It can also be `NULL` to run the task
#' sequentially via [base::sapply()]. The default value is `NULL`.
#'
-#' @param x A vector (i.e., usually of integers) to pass to the `fun` function.
+#' @param x An atomic vector or list to pass to the `fun` function.
#'
#' @param fun A function to apply to each element of `x`.
#'
#' @param ... Additional arguments to pass to the `fun` function.
#'
#' @details
-#' This function is a wrapper around the developer API of the
-#' [`parabar::parabar`] package. More specifically, this function:
-#' - Instantiates an appropriate [`parabar::parabar`] context. If the backend
-#' supports progress tracking (i.e., the backend is an instance of
-#' [`parabar::AsyncBackend`]), a progress tracking context (i.e.,
-#' [`parabar::ProgressTrackingContext`]) is instantiated and used. Otherwise, a
-#' regular context (i.e., [`parabar::Context`]) is instantiated. A regular
-#' context is also used if the progress tracking is disabled via the
-#' [`parabar::Options`] instance.
-#' - Registers the [`backend`][`parabar::Backend`] with the context.
-#' - Instantiates and configures the progress bar based on the
-#' [`parabar::Options`] instance in the session [`base::.Options`] list.
-#' - Executes the task in parallel, and displays a progress bar if appropriate.
+#' This function uses the [`parabar::UserApiConsumer`] class that acts like an
+#' interface for the developer API of the [`parabar::parabar`] package.
#'
#' @return
-#' A vector or list of the same length as `x` containing the results of the
-#' `fun`. The output format resembles that of [base::sapply()].
+#' A vector of the same length as `x` containing the results of the `fun`. The
+#' output format resembles that of [base::sapply()].
#'
#' @examples
#' \donttest{
@@ -95,5 +84,6 @@
#' @seealso
#' [parabar::start_backend()], [parabar::peek()], [parabar::export()],
#' [parabar::evaluate()], [parabar::clear()], [parabar::configure_bar()],
-#' [parabar::stop_backend()], [parabar::set_option()], [parabar::get_option()],
-#' [`parabar::Options`], and [`parabar::Service`].
+#' [parabar::par_lapply()], [parabar::stop_backend()], [parabar::set_option()],
+#' [parabar::get_option()], [`parabar::Options`], [`parabar::UserApiConsumer`],
+#' and [`parabar::Service`].
From 1a2709cec72936e4e81fe71c227d97305aa412c7 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:49:22 +0200
Subject: [PATCH 17/34] Docs: update package docs to mention `par_lapply`
---
man-roxygen/parabar.R | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/man-roxygen/parabar.R b/man-roxygen/parabar.R
index 37e0957..faef123 100644
--- a/man-roxygen/parabar.R
+++ b/man-roxygen/parabar.R
@@ -9,7 +9,7 @@
#' solution for parallel processing in their packages.
#'
#' @section Users:
-#' For the first category of users, [`parabar::parabar`] provides seven main
+#' For the first category of users, [`parabar::parabar`] provides several main
#' functions of interest:
#' - [parabar::start_backend()]: creates a parallel backend for executing tasks
#' according to the specifications provided.
@@ -19,6 +19,10 @@
#' [base::sapply()] function when no backend is provided. However, when a
#' backend is provided, the function will execute a task in parallel on the
#' backend, similar to the built-in function [parallel::parSapply()].
+#' - [parabar::par_lapply()]: is a drop-in replacement for the built-in
+#' [base::lapply()] function when no backend is provided. However, when a
+#' backend is provided, the function will execute a task in parallel on the
+#' backend, similar to the built-in function [parallel::parLapply()].
#' - [parabar::clear()]: removes all variables available on a backend.
#' - [parabar::peek()]: returns the names of all variables available on a
#' backend.
@@ -56,7 +60,8 @@
#' [`start()`][parabar::Service], [`stop()`][parabar::Service],
#' [`clear()`][parabar::Service], [`peek()`][parabar::Service],
#' [`export()`][parabar::Service], [`evaluate()`][parabar::Service],
-#' [`sapply()`][parabar::Service], and [`get_output()`][parabar::Service].
+#' [`sapply()`][parabar::Service], [`lapply()`][parabar::Service], and
+#' [`get_output()`][parabar::Service].
#'
#' Check out the documentation for [`parabar::Service`] for more information on
#' each method.
@@ -67,9 +72,9 @@
#' call to the corresponding backend method. However, a more complex context can
#' augment the operation before forwarding the call to the backend. One example
#' of a complex context is the [`parabar::ProgressTrackingContext`] class. This
-#' class extends the regular [`parabar::Context`] class and decorates the
-#' backend [`sapply()`][parabar::Service] operation to log the progress after
-#' each task execution and display a progress bar.
+#' class extends the regular [`parabar::Context`] class and decorates, for
+#' example, the backend [`sapply()`][parabar::Service] operation to log the
+#' progress after each task execution and display a progress bar.
#'
#' The following are the main classes provided by `parabar`:
#' - [`parabar::Service`]: interface for backend operations.
@@ -86,7 +91,8 @@
#' - [`parabar::BackendFactory`]: factory for creating backend objects.
#' - [`parabar::Context`]: default context for executing backend operations.
#' - [`parabar::ProgressTrackingContext`]: context for decorating the
-#' [`sapply()`][parabar::Service] operation to track and display progress.
+#' [`sapply()`][parabar::Service] and [`lapply()`][parabar::Service]
+#' operations to track and display the execution progress.
#' - [`parabar::ContextFactory`]: factory for creating context objects.
#'
#' @section Progress Bars:
@@ -97,7 +103,7 @@
#' - [`parabar::BasicBar`]: a simple, but robust, bar created via
#' [utils::txtProgressBar()] extending the [`parabar::Bar`] abstract class.
#' - [`parabar::ModernBar`]: a modern bar created via [`progress::progress_bar`]
-#' extending the [parabar::Bar] abstract class.
+#' extending the [`parabar::Bar`] abstract class.
#' - [`parabar::BarFactory`]: factory for creating bar objects.
#'
#' Finally, [`parabar::parabar`] uses several [base::options()] to configure the
From 5f26f1a262a3a72119a8a4433736ce4f813ff58e Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:50:23 +0200
Subject: [PATCH 18/34] Docs: mention `par_lapply` in other `API` functions
---
man-roxygen/clear.R | 2 +-
man-roxygen/evaluate.R | 2 +-
man-roxygen/export.R | 2 +-
man-roxygen/peek.R | 2 +-
man-roxygen/start-backend.R | 2 +-
man-roxygen/stop-backend.R | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/man-roxygen/clear.R b/man-roxygen/clear.R
index 6d9b2b0..61d6fa3 100644
--- a/man-roxygen/clear.R
+++ b/man-roxygen/clear.R
@@ -23,4 +23,4 @@
#' @seealso
#' [parabar::start_backend()], [parabar::peek()], [parabar::export()],
#' [parabar::evaluate()], [parabar::configure_bar()], [parabar::par_sapply()],
-#' [parabar::stop_backend()], and [`parabar::Service`].
+#' [parabar::par_lapply()], [parabar::stop_backend()], and [`parabar::Service`].
diff --git a/man-roxygen/evaluate.R b/man-roxygen/evaluate.R
index 277f4ee..88c6e0d 100644
--- a/man-roxygen/evaluate.R
+++ b/man-roxygen/evaluate.R
@@ -26,4 +26,4 @@
#' @seealso
#' [parabar::start_backend()], [parabar::peek()], [parabar::export()],
#' [parabar::clear()], [parabar::configure_bar()], [parabar::par_sapply()],
-#' [parabar::stop_backend()], and [`parabar::Service`].
+#' [parabar::par_lapply()], [parabar::stop_backend()], and [`parabar::Service`].
diff --git a/man-roxygen/export.R b/man-roxygen/export.R
index ee3770b..e751c45 100644
--- a/man-roxygen/export.R
+++ b/man-roxygen/export.R
@@ -29,4 +29,4 @@
#' @seealso
#' [parabar::start_backend()], [parabar::peek()], [parabar::evaluate()],
#' [parabar::clear()], [parabar::configure_bar()], [parabar::par_sapply()],
-#' [parabar::stop_backend()], and [`parabar::Service`].
+#' [parabar::par_lapply()], [parabar::stop_backend()], and [`parabar::Service`].
diff --git a/man-roxygen/peek.R b/man-roxygen/peek.R
index 6094290..88331f9 100644
--- a/man-roxygen/peek.R
+++ b/man-roxygen/peek.R
@@ -25,4 +25,4 @@
#' @seealso
#' [parabar::start_backend()], [parabar::export()], [parabar::evaluate()],
#' [parabar::clear()], [parabar::configure_bar()], [parabar::par_sapply()],
-#' [parabar::stop_backend()], and [`parabar::Service`].
+#' [parabar::par_lapply()], [parabar::stop_backend()], and [`parabar::Service`].
diff --git a/man-roxygen/start-backend.R b/man-roxygen/start-backend.R
index 4cbdc40..abcbbc8 100644
--- a/man-roxygen/start-backend.R
+++ b/man-roxygen/start-backend.R
@@ -120,4 +120,4 @@
#' @seealso
#' [parabar::peek()], [parabar::export()], [parabar::evaluate()],
#' [parabar::clear()], [parabar::configure_bar()], [parabar::par_sapply()],
-#' [parabar::stop_backend()], and [`parabar::Service`].
+#' [parabar::par_lapply()], [parabar::stop_backend()], and [`parabar::Service`].
diff --git a/man-roxygen/stop-backend.R b/man-roxygen/stop-backend.R
index 5b418f2..20563ee 100644
--- a/man-roxygen/stop-backend.R
+++ b/man-roxygen/stop-backend.R
@@ -26,4 +26,4 @@
#' @seealso
#' [parabar::start_backend()], [parabar::peek()], [parabar::export()],
#' [parabar::evaluate()], [parabar::clear()], [parabar::configure_bar()],
-#' [parabar::par_sapply()], and [`parabar::Service`].
+#' [parabar::par_sapply()], [parabar::par_lapply()], and [`parabar::Service`].
From f4a587bb2a2c7feeaa3718d7f1ff1ff07eab9958 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:51:25 +0200
Subject: [PATCH 19/34] Docs: update `README` to include `par_lapply`
---
README.md | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index c6fc378..c9f3f46 100644
--- a/README.md
+++ b/README.md
@@ -270,6 +270,7 @@ can be performed on a backend.
| export(backend, variables, environment)
| Export objects to a backend. |
| evaluate(backend, expression)
| Evaluate expressions on a backend. |
| par_sapply(backend, x, fun)
| Run tasks in parallel on a backend. |
+| par_lapply(backend, x, fun)
| Run tasks in parallel on a backend. |
Check the documentation corresponding to each operation for more information and
examples.
@@ -298,6 +299,7 @@ The `?Service` interface defines the following operations:
- `export`: Export variables from a given environment to the backend.
- `evaluate`: Evaluate an arbitrary expression on the backend.
- `sapply`: Run a task on the backend.
+- `lapply`: Run a task on the backend.
- `get_output`: Get the output of the task execution.
Check out the documentation for `Service` for more information on each method.
@@ -369,8 +371,8 @@ backend <- backend_factory$get("async")
backend$start(specification)
```
-Finally, we can run a task in parallel by calling the `sapply` method on the
-`backend` instance.
+Finally, we can run a task in parallel by calling, e.g., the `sapply` method on
+the `backend` instance.
```r
# Run a task in parallel.
@@ -484,6 +486,10 @@ Check out the UML diagram below for a quick overview of the package design.
+**_Note._** For the sake of clarity, the diagram only displays the `sapply`
+operation for running tasks in parallel. However, other operations are supported
+as well (i.e., see table in the section *Additional Operations*).
+
## Contributing
- Any contributions are welcome and greatly appreciated. Please open a [pull
request](https://github.com/mihaiconstantin/parabar/pulls) on `GitHub`.
From 8e7c3ad6539949ca9b5ede5dd92ff5d00dd089e1 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:52:21 +0200
Subject: [PATCH 20/34] Build: add `UserApiConsumer` to `Collate` in
`DESCRIPTION`
---
DESCRIPTION | 1 +
1 file changed, 1 insertion(+)
diff --git a/DESCRIPTION b/DESCRIPTION
index 3017287..362982e 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -41,6 +41,7 @@ Collate:
'ProgressTrackingContext.R'
'ContextFactory.R'
'Warning.R'
+ 'UserApiConsumer.R'
'exports.r'
'logo.R'
'parabar-package.R'
From 6c21fa70ed48e2485acafda356c8cee755daba70 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:53:18 +0200
Subject: [PATCH 21/34] Build: update package imports and `NAMESPACE`
---
NAMESPACE | 2 +-
R/parabar-package.R | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/NAMESPACE b/NAMESPACE
index 7edd5ec..4ef7702 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -41,7 +41,7 @@ importFrom(parallel,clusterEvalQ)
importFrom(parallel,clusterExport)
importFrom(parallel,detectCores)
importFrom(parallel,makeCluster)
-importFrom(parallel,parApply)
+importFrom(parallel,parLapply)
importFrom(parallel,parSapply)
importFrom(parallel,stopCluster)
importFrom(progress,progress_bar)
diff --git a/R/parabar-package.R b/R/parabar-package.R
index 7f57481..21a4fb5 100644
--- a/R/parabar-package.R
+++ b/R/parabar-package.R
@@ -17,7 +17,7 @@
# Imports.
#' @importFrom parallel detectCores makeCluster stopCluster clusterExport
-#' @importFrom parallel clusterEvalQ parSapply parApply clusterCall
+#' @importFrom parallel clusterEvalQ parSapply parLapply clusterCall
#' @importFrom R6 R6Class
#' @importFrom progress progress_bar
#' @importFrom callr r_session
From 261706e68dda505dfcef606a9d1453d722564954 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:54:24 +0200
Subject: [PATCH 22/34] Build: update `NAMESPACE` with new exports
---
NAMESPACE | 2 ++
1 file changed, 2 insertions(+)
diff --git a/NAMESPACE b/NAMESPACE
index 4ef7702..669ec08 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -19,6 +19,7 @@ export(Service)
export(Specification)
export(SyncBackend)
export(TaskState)
+export(UserApiConsumer)
export(Warning)
export(clear)
export(configure_bar)
@@ -26,6 +27,7 @@ export(evaluate)
export(export)
export(get_option)
export(make_logo)
+export(par_lapply)
export(par_sapply)
export(peek)
export(set_default_options)
From d1a781316c3bac78686cd67424d85351cada826a Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:55:58 +0200
Subject: [PATCH 23/34] Test: add and refactor test helpers
---
tests/testthat/helpers.R | 517 ++++++++++++++++++++++++++++++++-------
1 file changed, 424 insertions(+), 93 deletions(-)
diff --git a/tests/testthat/helpers.R b/tests/testthat/helpers.R
index 2e63e60..99ad801 100644
--- a/tests/testthat/helpers.R
+++ b/tests/testthat/helpers.R
@@ -1,5 +1,7 @@
# Helpers for testing.
+#region General test helpers.
+
# Helper for extracting the message associated with errors and warnings.
as_text <- function(expression) {
# Capture message.
@@ -66,26 +68,49 @@ task_is_running <- function(backend) {
return(status)
}
+#endregion
-# Set of tests for synchronous backend operations.
-tests_set_for_synchronous_backend_operations <- function(service, specification, task) {
- # Start the cluster on the backend.
- service$start(specification)
- # Always stop on exit.
- on.exit({
- # Stop the backend.
- service$stop()
- })
+#region Tests sets applicable to all backends types.
- # Expect that the cluster is empty upon creation.
- expect_true(all(sapply(service$peek(), length) == 0))
+# Set of tests for unimplemented service methods.
+tests_set_for_unimplemented_service_methods <- function(service) {
+ # Expect an error when calling the `start` method.
+ expect_error(service$start(), as_text(Exception$method_not_implemented()))
+
+ # Expect an error when calling the `stop` method.
+ expect_error(service$stop(), as_text(Exception$method_not_implemented()))
+
+ # Expect an error when calling the `clear` method.
+ expect_error(service$clear(), as_text(Exception$method_not_implemented()))
+
+ # Expect an error when calling the `peek` method.
+ expect_error(service$peek(), as_text(Exception$method_not_implemented()))
+
+ # Expect an error when calling the `export` method.
+ expect_error(service$export(), as_text(Exception$method_not_implemented()))
+
+ # Expect an error when calling the `evaluate` method.
+ expect_error(service$evaluate(), as_text(Exception$method_not_implemented()))
+
+ # Expect an error when calling the `sapply` method.
+ expect_error(service$sapply(), as_text(Exception$method_not_implemented()))
+
+ # Expect an error when calling the `lapply` method.
+ expect_error(service$lapply(), as_text(Exception$method_not_implemented()))
+
+ # Expect an error when calling the `get_output` method.
+ expect_error(service$get_output(), as_text(Exception$method_not_implemented()))
+}
- # Create a variable in a new environment.
+
+# Set of tests for exporting to the backend (i.e,. regardless of type).
+tests_set_for_backend_exporting <- function(service) {
+ # Create a variable in a different environment.
env <- new.env()
env$test_variable <- rnorm(1)
- # Export the variable from the environment to the backend.
+ # Export the variable from the specific environment to the backend.
service$export("test_variable", env)
# Expect that the variable is on the backend.
@@ -94,37 +119,70 @@ tests_set_for_synchronous_backend_operations <- function(service, specification,
# Expect the cluster to hold the correct value for the exported variable.
expect_true(all(service$evaluate(test_variable) == env$test_variable))
- # Clear the backend.
- service$clear()
+ # Assign a variable to the current environment.
+ assign("test_variable", rnorm(1), envir = environment())
- # Expect that clearing the cluster leaves it empty.
- expect_true(all(sapply(service$peek(), length) == 0))
+ # Export the variable using the current environment (i.e., parent of `export`).
+ service$export("test_variable")
- # Select task arguments for the `sapply` operation.
- x <- sample(1:100, 100)
- y <- sample(1:100, 1)
- z <- sample(1:100, 1)
- sleep = sample(c(0, 0.001, 0.002), 1)
+ # Expect that the variable is on the backend.
+ expect_true(all(service$peek() == "test_variable"))
+
+ # Expect the cluster to hold the correct value for the exported variable.
+ expect_true(all(service$evaluate(test_variable) == get("test_variable", envir = environment())))
+}
- # Run the task in parallel.
- service$sapply(x, task, y = y, z = z, sleep = sleep)
+
+# Set of tests for starting and stopping backends.
+tests_set_for_backend_states <- function(backend, specification) {
+ # Expect an error if an attempt is made to start a cluster while one is already active.
+ expect_error(backend$start(specification), as_text(Exception$cluster_active()))
+
+ # Stop the backend.
+ backend$stop()
+
+ # Expect that stopping the cluster marks it as inactive.
+ expect_false(backend$active)
+
+ # Expect the cluster field has been cleared.
+ expect_null(backend$cluster)
+
+ # Start a new cluster on the same backend instance.
+ backend$start(specification)
+
+ # Expect the cluster is active.
+ expect_true(backend$active)
+
+ # Stop the cluster.
+ backend$stop()
+
+ # Expect that trying to stop a cluster that is not active throws an error.
+ expect_error(backend$stop(), as_text(Exception$cluster_not_active()))
+}
+
+#endregion
+
+
+#region Tests sets for synchronous backends.
+
+# Set of tests for the synchronous backend task execution via a specified operation.
+tests_set_for_synchronous_backend_task_execution <- function(operation, service, expected_output) {
+ # Run the task in parallel via the requested operation (e.g., `sapply`).
+ eval(operation)
# Expect the that output is correct.
- expect_equal(service$get_output(), task(x, y, z))
+ expect_equal(service$get_output(), expected_output)
# Expect that subsequent calls to `get_output` return `NULL`.
expect_null(service$get_output())
- # Expect that the cluster is empty after performing operations on it.
- expect_true(all(sapply(service$peek(), length) == 0))
-
# Remain silent.
invisible(NULL)
}
# Set of tests for synchronous backend operations.
-tests_set_for_asynchronous_backend_operations <- function(service, specification, task) {
+tests_set_for_synchronous_backend_operations <- function(service, specification, task) {
# Start the cluster on the backend.
service$start(specification)
@@ -137,34 +195,55 @@ tests_set_for_asynchronous_backend_operations <- function(service, specification
# Expect that the cluster is empty upon creation.
expect_true(all(sapply(service$peek(), length) == 0))
- # Create a variable in a new environment.
- env <- new.env()
- env$test_variable <- rnorm(1)
-
- # Export the variable from the environment to the backend.
- service$export("test_variable", env)
+ # Tests for exporting to the backend.
+ tests_set_for_backend_exporting(service)
- # Expect that the variable is on the backend.
- expect_true(all(service$peek() == "test_variable"))
-
- # Expect the cluster to hold the correct value for the exported variable.
- expect_true(all(service$evaluate(test_variable) == env$test_variable))
+ # Clear the backend.
+ service$clear()
# Expect that clearing the cluster leaves it empty.
- service$clear()
expect_true(all(sapply(service$peek(), length) == 0))
- # Select task arguments for the `sapply` operation.
+ # Select task arguments.
x <- sample(1:100, 100)
y <- sample(1:100, 1)
z <- sample(1:100, 1)
sleep = sample(c(0, 0.001, 0.002), 1)
- # Compute the correct output.
+ # Created the expect output.
expected_output <- task(x, y, z)
+ # Define the `sapply` operation.
+ operation <- bquote(service$sapply(.(x), .(task), y = .(y), z = .(z), sleep = .(sleep)))
+
+ # Tests for the `sapply` operation.
+ tests_set_for_synchronous_backend_task_execution(operation, service, expected_output)
+
+ # Created the expect output.
+ expected_output <- as.list(expected_output)
+
+ # Define the `lapply` operation.
+ operation <- bquote(service$lapply(.(x), .(task), y = .(y), z = .(z), sleep = .(sleep)))
+
+ # Tests for the `lapply` operation.
+ tests_set_for_synchronous_backend_task_execution(operation, service, expected_output)
+
+ # Expect that the cluster is empty after performing operations on it.
+ expect_true(all(sapply(service$peek(), length) == 0))
+
+ # Remain silent.
+ invisible(NULL)
+}
+
+#endregion
+
+
+#region Tests sets for asynchronous backends.
+
+# Set of tests for the asynchronous backend task execution via a specified operation.
+tests_set_for_asynchronous_backend_task_execution <- function(operation, service, expected_output) {
# Run the task in parallel.
- service$sapply(x, task, y = y, z = z)
+ eval(operation)
# Expect the that output is correct.
expect_equal(service$get_output(wait = TRUE), expected_output)
@@ -173,16 +252,16 @@ tests_set_for_asynchronous_backend_operations <- function(service, specification
expect_error(service$get_output(), as_text(Exception$async_task_not_started()))
# Run the task in parallel, with a bit of overhead.
- service$sapply(x, task, y = y, z = z, sleep = sleep)
+ eval(operation)
# Expect that trying to run a task while another is running fails.
- expect_error(service$sapply(x, task, y = y, z = z), as_text(Exception$async_task_running()))
+ expect_error(eval(operation), as_text(Exception$async_task_running()))
# Expect the that output is correct.
expect_equal(service$get_output(wait = TRUE), expected_output)
- # Run the task in parallel.
- service$sapply(x, task, y = y, z = z, sleep = sleep)
+ # Run the task in parallel, with a bit of overhead.
+ eval(operation)
# Expect that trying to get the output of a task that is still running fails.
expect_error(service$get_output(), as_text(Exception$async_task_running()))
@@ -194,10 +273,62 @@ tests_set_for_asynchronous_backend_operations <- function(service, specification
}
# Expect that trying to run a task without reading the previous output fails.
- expect_error(service$sapply(data, task, add = add), as_text(Exception$async_task_completed()))
+ expect_error(eval(operation), as_text(Exception$async_task_completed()))
# Expect the that output is correct.
expect_equal(service$get_output(), expected_output)
+}
+
+
+# Set of tests for synchronous backend operations.
+tests_set_for_asynchronous_backend_operations <- function(service, specification, task) {
+ # Start the cluster on the backend.
+ service$start(specification)
+
+ # Always stop on exit.
+ on.exit({
+ # Stop the backend.
+ service$stop()
+ })
+
+ # Expect that the cluster is empty upon creation.
+ expect_true(all(sapply(service$peek(), length) == 0))
+
+ # Tests for the `export` operation.
+ tests_set_for_backend_exporting(service)
+
+ # Clear the backend.
+ service$clear()
+
+ # Expect that clearing the cluster leaves it empty.
+ expect_true(all(sapply(service$peek(), length) == 0))
+
+ # Expect error waiting to fetch the output when no task is running.
+ expect_error(service$get_output(wait = TRUE), as_text(Exception$async_task_not_started()))
+
+ # Select task arguments.
+ x <- sample(1:100, 100)
+ y <- sample(1:100, 1)
+ z <- sample(1:100, 1)
+ sleep = sample(c(0, 0.001, 0.002), 1)
+
+ # Compute the expected output for the `sapply` operation.
+ expected_output <- task(x, y, z)
+
+ # Define the `sapply` operation.
+ operation <- bquote(service$sapply(.(x), .(task), y = .(y), z = .(z), sleep = .(sleep)))
+
+ # Tests for the `sapply` operation.
+ tests_set_for_asynchronous_backend_task_execution(operation, service, expected_output)
+
+ # Compute the expected output for the `lapply` operation.
+ expected_output <- as.list(expected_output)
+
+ # Define the `lapply` operation.
+ operation <- bquote(service$lapply(.(x), .(task), y = .(y), z = .(z), sleep = .(sleep)))
+
+ # Tests for the `lapply` operation.
+ tests_set_for_asynchronous_backend_task_execution(operation, service, expected_output)
# Expect that the cluster is empty after performing operations on it.
expect_true(all(sapply(service$peek(), length) == 0))
@@ -206,62 +337,100 @@ tests_set_for_asynchronous_backend_operations <- function(service, specification
invisible(NULL)
}
+#endregion
-# Set of tests for starting and stopping backends.
-tests_set_for_backend_states <- function(backend, specification) {
- # Expect an error if an attempt is made to start a cluster while one is already active.
- expect_error(backend$start(specification), as_text(Exception$cluster_active()))
- # Stop the backend.
- backend$stop()
+#region Tests sets for progress tracking.
- # Expect that stopping the cluster marks it as inactive.
- expect_false(backend$active)
+# Set of tests for executing tasks in a progress tracking context with output.
+tests_set_for_task_execution_with_progress_tracking <- function(operation, context, expected_output) {
+ # Clear the progress output on exit.
+ on.exit({
+ # Clear the output.
+ context$progress_bar_output <- NULL
+ })
- # Expect the cluster field has been cleared.
- expect_null(backend$cluster)
+ # Create a bar factory.
+ bar_factory <- BarFactory$new()
- # Start a new cluster on the same backend instance.
- backend$start(specification)
+ # Get a basic bar instance.
+ bar <- bar_factory$get("basic")
- # Expect the cluster is active.
- expect_true(backend$active)
+ # Register the bar with the context object.
+ context$set_bar(bar)
- # Stop the cluster.
- backend$stop()
+ # Configure the bar.
+ context$configure_bar(
+ style = 3
+ )
- # Expect that trying to stop a cluster that is not active throws an error.
- expect_error(backend$stop(), as_text(Exception$cluster_not_active()))
-}
+ # Run the task in parallel.
+ eval(operation)
+ # Expect that the task output is correct.
+ expect_equal(context$get_output(wait = TRUE), expected_output)
-# Set of tests for unimplemented service methods.
-tests_set_for_unimplemented_service_methods <- function(service) {
- # Expect an error when calling the `start` method.
- expect_error(service$start(), as_text(Exception$method_not_implemented()))
+ # Expect the progress bar was shown correctly.
+ expect_true(any(grepl("=\\| 100%", context$progress_bar_output)))
- # Expect an error when calling the `stop` method.
- expect_error(service$stop(), as_text(Exception$method_not_implemented()))
+ # Get a modern bar instance.
+ bar <- bar_factory$get("modern")
- # Expect an error when calling the `clear` method.
- expect_error(service$clear(), as_text(Exception$method_not_implemented()))
+ # Register the bar with the same context object.
+ context$set_bar(bar)
- # Expect an error when calling the `peek` method.
- expect_error(service$peek(), as_text(Exception$method_not_implemented()))
+ # Configure the bar.
+ context$configure_bar(
+ show_after = 0,
+ format = ":bar| :percent",
+ clear = FALSE,
+ force = TRUE
+ )
- # Expect an error when calling the `export` method.
- expect_error(service$export(), as_text(Exception$method_not_implemented()))
+ # Run the task in parallel.
+ eval(operation)
- # Expect an error when calling the `evaluate` method.
- expect_error(service$evaluate(), as_text(Exception$method_not_implemented()))
+ # Expect that the task output is correct.
+ expect_equal(context$get_output(wait = TRUE), expected_output)
- # Expect an error when calling the `sapply` method.
- expect_error(service$sapply(), as_text(Exception$method_not_implemented()))
+ # Expect the progress bar was shown correctly.
+ expect_true(any(grepl("=\\| 100%", context$progress_bar_output)))
+}
- # Expect an error when calling the `get_output` method.
- expect_error(service$get_output(), as_text(Exception$method_not_implemented()))
+# Set of tests for progress tracking context.
+tests_set_for_progress_tracking_context <- function(context, task) {
+ # Check the type.
+ Helper$check_object_type(context, "ProgressTrackingContextTester")
+
+ # Select task arguments.
+ x <- sample(1:100, 100)
+ y <- sample(1:100, 1)
+ z <- sample(1:100, 1)
+ sleep = sample(c(0, 0.001, 0.002), 1)
+
+ # Create the expected output for the `sapply` operation.
+ expected_output <- task(x, y, z)
+
+ # Create the `sapply` operation.
+ operation <- bquote(context$sapply(.(x), .(task), y = .(y), z = .(z), sleep = .(sleep)))
+
+ # Tests for the `sapply` operation in a progress tracking context.
+ tests_set_for_task_execution_with_progress_tracking(operation, context, expected_output)
+
+ # Create the expected output for the `lapply` operation.
+ expected_output <- as.list(expected_output)
+
+ # Create the `lapply` operation.
+ operation <- bquote(context$lapply(.(x), .(task), y = .(y), z = .(z), sleep = .(sleep)))
+
+ # Tests for the `lapply` operation in a progress tracking context.
+ tests_set_for_task_execution_with_progress_tracking(operation, context, expected_output)
}
+#endregion
+
+
+#region Tests sets for the user API.
# Set of tests for creating backends via the user API.
tests_set_for_backend_creation_via_user_api <- function(cluster_type, backend_type) {
@@ -289,6 +458,133 @@ tests_set_for_backend_creation_via_user_api <- function(cluster_type, backend_ty
}
+# Set of tests for task execution via the user API.
+tests_set_for_user_api_task_execution <- function(parallel, sequential, expected_output) {
+ # Clean-up.
+ on.exit({
+ # Set default values for package options.
+ set_default_options()
+ })
+
+ # Select a cluster type.
+ cluster_type <- pick_cluster_type(Specification$new()$types)
+
+ # Disable progress tracking.
+ set_option("progress_track", FALSE)
+
+ # Create a synchronous backend.
+ backend <- start_backend(
+ cores = 2,
+ cluster_type = cluster_type,
+ backend_type = "sync"
+ )
+
+ # Expect the output of the task ran in parallel to be correct.
+ expect_equal(eval(parallel), expected_output)
+
+ # Enable progress tracking.
+ set_option("progress_track", TRUE)
+
+ # Expect warning for requesting progress tracking with incompatible backend.
+ expect_warning(
+ eval(parallel),
+ as_text(Warning$progress_not_supported_for_backend(backend))
+ )
+
+ # Stop the synchronous backend.
+ stop_backend(backend)
+
+ # Create an asynchronous backend.
+ backend <- start_backend(
+ cores = 2,
+ cluster_type = cluster_type,
+ backend_type = "async"
+ )
+
+ # Expect the output to be correct.
+ expect_equal(eval(parallel), expected_output)
+
+ # Disable progress tracking.
+ set_option("progress_track", FALSE)
+
+ # Expect the output to be correct.
+ expect_equal(eval(parallel), expected_output)
+
+ # Stop the asynchronous backend.
+ stop_backend(backend)
+
+ # Expect the task to produce correct output when ran sequentially.
+ expect_equal(eval(sequential), expected_output)
+}
+
+
+# Set of tests for progress tracking via the user API.
+tests_set_for_user_api_progress_tracking <- function(operation) {
+ # Pick a cluster type.
+ cluster_type <- pick_cluster_type(Specification$new()$types)
+
+ # Create an asynchronous backend.
+ backend <- start_backend(
+ cores = 2,
+ cluster_type = cluster_type,
+ backend_type = "async"
+ )
+
+ # Clean-up on exit.
+ on.exit({
+ # Stop the backend.
+ stop_backend(backend)
+
+ # Restore the default options.
+ set_default_options()
+ })
+
+ # Configure modern bar.
+ configure_bar(
+ type = "modern",
+ force = TRUE,
+ clear = FALSE
+ )
+
+ # Redirect output.
+ sink("/dev/null", type = "output")
+
+ # Run the task and capture the progress bar output.
+ output <- capture.output({ eval(operation) }, type = "message")
+
+ # Remove output redirection.
+ sink(NULL)
+
+ # Expect the progress bar to be shown correctly.
+ expect_true(grepl("tasks \\[100%\\]", paste0(output, collapse = ""), perl = TRUE))
+
+ # Configure the basic bar.
+ configure_bar(
+ type = "basic",
+ style = 3
+ )
+
+ # Run the task and capture the progress bar output.
+ output <- capture.output({ eval(operation) }, type = "output")
+
+ # Expect the progress bar to be shown correctly.
+ expect_true(grepl("=\\| 100%", paste0(output, collapse = ""), perl = TRUE))
+
+ # Disable progress tracking.
+ set_option("progress_track", FALSE)
+
+ # Run the task and capture the output without the progress bar.
+ output <- capture.output({ eval(operation) }, type = "output")
+
+ # Expect the progress bar to be missing from the output.
+ expect_false(grepl("=\\| 100%", paste0(output, collapse = ""), perl = TRUE))
+}
+
+#endregion
+
+
+#region Helper `R6` classes for testing.
+
# Helper for testing private methods of `Specification` class.
SpecificationTester <- R6::R6Class("SpecificationTester",
inherit = Specification,
@@ -340,12 +636,9 @@ BackendImplementation <- R6::R6Class("BackendImplementation",
ProgressTrackingContextTester <- R6::R6Class("ProgressTrackingContextTester",
inherit = ProgressTrackingContext,
- public = list(
- # The progress bar output used for testing.
- progress_bar_output = NULL,
-
- # Implementation for the `sapply` method preserving the log file.
- sapply = function(x, fun, ...) {
+ private = list(
+ # Wrapper for executing task operations with progress output capturing.
+ .execute_and_capture_progress = function(operation) {
# Create a text connection.
connection <- textConnection("output", open = "w", local = TRUE)
@@ -364,13 +657,49 @@ ProgressTrackingContextTester <- R6::R6Class("ProgressTrackingContextTester",
})
# Execute the task.
- super$sapply(x, fun, ...)
+ eval(operation)
# Store the progress bar output on the instance.
self$progress_bar_output <- output
}
),
+ public = list(
+ # The progress bar output used for testing.
+ progress_bar_output = NULL,
+
+ # Implementation for the `sapply` method capturing the progress output.
+ sapply = function(x, fun, ...) {
+ # Define the operation.
+ operation <- bquote(
+ do.call(
+ super$sapply, c(list(.(x), .(fun)), .(list(...)))
+ )
+ )
+
+ # Execute the task via the operation and capture the progress output.
+ private$.execute_and_capture_progress(operation)
+ },
+
+ # Implementation for the `lapply` method capturing the progress output.
+ lapply = function(x, fun, ...) {
+ # Define the operation.
+ operation <- bquote(
+ do.call(
+ super$lapply, c(list(.(x), .(fun)), .(list(...)))
+ )
+ )
+
+ # Execute the task via the operation and capture the progress output.
+ private$.execute_and_capture_progress(operation)
+ },
+
+ # Wrapper to expose `.make_log` for testing.
+ make_log = function() {
+ private$.make_log()
+ }
+ ),
+
active = list(
# Expose the bar configuration.
bar_config = function() { return(private$.bar_config) }
@@ -387,3 +716,5 @@ BarImplementation <- R6::R6Class("BarImplementation",
initialize = function() {}
)
)
+
+#endregion
From d0c2828949e3dfd34007d72ec9ef325e4114c2e9 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:56:20 +0200
Subject: [PATCH 24/34] Test: update test for `AsyncBackend` class
---
tests/testthat/test-async-backend.R | 3 +++
1 file changed, 3 insertions(+)
diff --git a/tests/testthat/test-async-backend.R b/tests/testthat/test-async-backend.R
index 67f38e0..bb3ec3a 100644
--- a/tests/testthat/test-async-backend.R
+++ b/tests/testthat/test-async-backend.R
@@ -36,6 +36,9 @@ test_that("'AsyncBackend' creates and manages clusters correctly", {
# Test backend states.
tests_set_for_backend_states(backend, specification)
+
+ # Expect error attempting to get the cluster state for an inactive backend.
+ expect_error(backend$task_state, as_text(Exception$cluster_not_active()))
})
From 1a1e7601992b3f42de5354f912f9e8a4046513c4 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:57:18 +0200
Subject: [PATCH 25/34] Test: update tests for `ProgressTrackingContext` class
---
.../testthat/test-progress-tracking-context.R | 110 +++++++++---------
1 file changed, 56 insertions(+), 54 deletions(-)
diff --git a/tests/testthat/test-progress-tracking-context.R b/tests/testthat/test-progress-tracking-context.R
index 397d1bb..100bad8 100644
--- a/tests/testthat/test-progress-tracking-context.R
+++ b/tests/testthat/test-progress-tracking-context.R
@@ -33,7 +33,7 @@ test_that("'ProgressTrackingContext' sets the backend correctly", {
})
-test_that("'ProgressTrackingContext' sets progress bars correctly", {
+test_that("'ProgressTrackingContext' sets the progress bar correctly", {
# Create a bar factory.
bar_factory <- BarFactory$new()
@@ -90,16 +90,61 @@ test_that("'ProgressTrackingContext' configures the progress bar correctly", {
})
-test_that("'ProgressTrackingContext' executes the task in parallel correctly", {
- # Select task arguments for the `sapply` operation.
- x <- sample(1:100, 100)
- y <- sample(1:100, 1)
- z <- sample(1:100, 1)
- sleep = sample(c(0, 0.001, 0.002), 1)
+test_that("'ProgressTrackingContext' correctly creates log files.", {
+ # Reset default package options on exit.
+ on.exit({
+ # Set defaults.
+ set_default_options()
+ })
+
+ # Create a progress tracking context object.
+ context <- ProgressTrackingContextTester$new()
+
+ # Create a log file with a randomly generated path.
+ path <- context$make_log()
+
+ # Expect that the file exist at the used path.
+ expect_true(file.exists(path))
+
+ # Remove the file.
+ file.remove(path)
+
+ # Pick a specific log path.
+ log_path <- tempfile(pattern = "progress_log")
+
+ # Fix the log path.
+ set_option("progress_log_path", log_path)
+
+ # Create a log file with the fixed log path.
+ path <- context$make_log()
+
+ # Expect that the correct log path was used.
+ expect_equal(log_path, path)
+
+ # Expect that the log file was created at the fixed path.
+ expect_true(file.exists(path))
- # Compute the correct output.
- expected_output <- test_task(x, y, z)
+ # Remove the file.
+ file.remove(path)
+ # Pick an absurd path for the log file.
+ log_path_absurd <- "/absurd/log/file/path"
+
+ # Fix the log path to the absurd value.
+ set_option("progress_log_path", log_path_absurd)
+
+ # Expect error when failing to create the log file.
+ expect_error(
+ context$make_log(),
+ as_text(Exception$temporary_file_creation_failed(log_path_absurd))
+ )
+
+ # Expect that the log file was not created at the absurd path.
+ expect_false(file.exists(log_path_absurd))
+})
+
+
+test_that("'ProgressTrackingContext' executes the task in parallel correctly", {
# Create a specification.
specification <- Specification$new()
@@ -124,51 +169,8 @@ test_that("'ProgressTrackingContext' executes the task in parallel correctly", {
# Start the backend.
context$start(specification)
- # Create a bar factory.
- bar_factory <- BarFactory$new()
-
- # Get a basic bar instance.
- bar <- bar_factory$get("basic")
-
- # Register the bar with the context object.
- context$set_bar(bar)
-
- # Configure the bar.
- context$configure_bar(
- style = 3
- )
-
- # Run the task in parallel.
- context$sapply(x, test_task, y = y, z = z, sleep = sleep)
-
- # Expect that the task output is correct.
- expect_equal(context$get_output(wait = TRUE), expected_output)
-
- # Expect the progress bar was shown correctly.
- expect_true(any(grepl("=\\| 100%", context$progress_bar_output)))
-
- # Get a modern bar instance.
- bar <- bar_factory$get("modern")
-
- # Register the bar with the same context object.
- context$set_bar(bar)
-
- # Configure the bar.
- context$configure_bar(
- show_after = 0,
- format = ":bar| :percent",
- clear = FALSE,
- force = TRUE
- )
-
- # Run the task in parallel.
- context$sapply(x, test_task, y = y, z = z, sleep = sleep)
-
- # Expect that the task output is correct.
- expect_equal(context$get_output(wait = TRUE), expected_output)
-
- # Expect the progress bar was shown correctly.
- expect_true(any(grepl("=\\| 100%", context$progress_bar_output)))
+ # Expect correctly executed tasks and logged progress.
+ tests_set_for_progress_tracking_context(context, test_task)
# Stop the backend.
context$stop()
From 093aefe694074037d639ffef6998953ad0a905de Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 15:57:46 +0200
Subject: [PATCH 26/34] Test: update tests for the user `API` functions
---
tests/testthat/test-user-api.R | 166 ++++++++-------------------------
1 file changed, 37 insertions(+), 129 deletions(-)
diff --git a/tests/testthat/test-user-api.R b/tests/testthat/test-user-api.R
index 129487c..e27fe9e 100644
--- a/tests/testthat/test-user-api.R
+++ b/tests/testthat/test-user-api.R
@@ -223,7 +223,12 @@ test_that("user API functions handle incompatible input correctly", {
)
expect_error(
- par_sapply(backend),
+ par_sapply(backend, NULL, NULL),
+ as_text(Exception$type_not_assignable(class(backend), "Backend"))
+ )
+
+ expect_error(
+ par_lapply(backend, NULL, NULL),
as_text(Exception$type_not_assignable(class(backend), "Backend"))
)
})
@@ -278,156 +283,59 @@ test_that("user API functions handle backend operations correctly", {
})
-test_that("'par_sapply' correctly runs tasks in parallel", {
- # Clean-up.
- on.exit({
- # Set default values for package options.
- set_default_options()
- })
-
- # Select task arguments for the `sapply` operation.
+test_that("user API functions run tasks in parallel correctly", {
+ # Select task arguments.
x <- sample(1:100, 100)
y <- sample(1:100, 1)
z <- sample(1:100, 1)
sleep = sample(c(0, 0.001, 0.002), 1)
- # Compute the correct output.
+ # Compute the expected output for the `par_sapply` user API function.
expected_output <- test_task(x, y, z)
- # Select a cluster type.
- cluster_type <- pick_cluster_type(Specification$new()$types)
-
- # Disable progress tracking.
- set_option("progress_track", FALSE)
-
- # Create a synchronous backend.
- backend <- start_backend(
- cores = 2,
- cluster_type = cluster_type,
- backend_type = "sync"
+ # Define the `par_sapply` parallel operation.
+ parallel_sapply <- bquote(
+ par_sapply(backend, x = .(x), fun = test_task, .(y), .(z), sleep = .(sleep)),
)
- # Expect the output of the task ran in parallel to be correct.
- expect_equal(
- par_sapply(backend, x = x, fun = test_task, y, z, sleep = sleep),
- expected_output
+ # Define the `par_sapply` sequential operation.
+ sequential_sapply <- bquote(
+ par_sapply(backend = NULL, x = .(x), fun = test_task, .(y), .(z)),
)
- # Enable progress tracking.
- set_option("progress_track", TRUE)
+ # Expect the `par_sapply` to run the task in parallel correctly.
+ tests_set_for_user_api_task_execution(parallel_sapply, sequential_sapply, expected_output)
- # Expect warning for requesting progress tracking with incompatible backend.
- expect_warning(
- par_sapply(backend, x = x, fun = test_task, y, z, sleep = sleep),
- as_text(Warning$progress_not_supported_for_backend(backend))
- )
-
- # Stop the synchronous backend.
- stop_backend(backend)
+ # Compute the expected output for the `par_lapply` user API function.
+ expected_output <- as.list(expected_output)
- # Create an asynchronous backend.
- backend <- start_backend(
- cores = 2,
- cluster_type = cluster_type,
- backend_type = "async"
+ # Define the `par_lapply` parallel operation.
+ parallel_lapply <- bquote(
+ par_lapply(backend, x = .(x), fun = test_task, .(y), .(z), sleep = .(sleep)),
)
- # Expect the output to be correct.
- expect_equal(
- par_sapply(backend, x = x, fun = test_task, y, z, sleep = sleep),
- expected_output
+ # Define the `par_lapply` sequential operation.
+ sequential_lapply <- bquote(
+ par_lapply(backend = NULL, x = .(x), fun = test_task, .(y), .(z)),
)
- # Disable progress tracking.
- set_option("progress_track", FALSE)
-
- # Expect the output to be correct.
- expect_equal(
- par_sapply(backend, x = x, fun = test_task, y, z, sleep = sleep),
- expected_output
- )
-
- # Stop the asynchronous backend.
- stop_backend(backend)
-
- # Expect the task to produce correct output when ran sequentially.
- expect_equal(
- par_sapply(backend = NULL, x = x, fun = test_task, y, z),
- expected_output
- )
+ # Expect the `par_lapply` to run the task in parallel correctly.
+ tests_set_for_user_api_task_execution(parallel_lapply, sequential_lapply, expected_output)
})
-test_that("'par_sapply' tracks progress correctly", {
+test_that("user API functions track progress correctly", {
# Run the test only in interactive contexts.
if (interactive()) {
- # Pick a cluster type.
- cluster_type <- pick_cluster_type(Specification$new()$types)
-
- # Create an asynchronous backend.
- backend <- start_backend(
- cores = 2,
- cluster_type = cluster_type,
- backend_type = "async"
- )
-
- # Clean-up on exit.
- on.exit({
- # Stop the backend.
- stop_backend(backend)
-
- # Restore the default options.
- set_default_options()
- })
-
- # Configure modern bar.
- configure_bar(
- type = "modern",
- force = TRUE,
- clear = FALSE
- )
-
- # Redirect output.
- sink("/dev/null", type = "output")
-
- # Run the task and capture the progress bar output.
- output <- capture.output({
- par_sapply(backend, x = 1:100, fun = test_task, 1, 2)
- }, type = "message"
- )
-
- # Remove output redirection.
- sink(NULL)
-
- # Expect the progress bar to be shown correctly.
- expect_true(grepl("tasks \\[100%\\]", paste0(output, collapse = ""), perl = TRUE))
-
- # Configure the basic bar.
- configure_bar(
- type = "basic",
- style = 3
- )
-
- # Run the task and capture the progress bar output.
- output <- capture.output({
- par_sapply(backend, x = 1:100, fun = test_task, 1, 2)
- }, type = "output"
- )
-
- # Expect the progress bar to be shown correctly.
- expect_true(grepl("=\\| 100%", paste0(output, collapse = ""), perl = TRUE))
-
- # Disable progress tracking.
- set_option("progress_track", FALSE)
-
- # Run the task and capture the output without the progress bar.
- output <- capture.output({
- par_sapply(backend, x = 1:100, fun = test_task, 1, 2)
- }, type = "output"
- )
-
- # Expect the progress bar to be missing from the output.
- expect_false(grepl("=\\| 100%", paste0(output, collapse = ""), perl = TRUE))
+ # Expect progress tracking is displayed correctly via `par_sapply`.
+ tests_set_for_user_api_progress_tracking(bquote(
+ par_sapply(backend, x = 1:100, fun = test_task, 1, 2)
+ ))
+
+ # Expect progress tracking is displayed correctly via `par_lapply`.
+ tests_set_for_user_api_progress_tracking(bquote(
+ par_lapply(backend, x = 1:100, fun = test_task, 1, 2)
+ ))
} else {
skip("Test only runs in interactive contexts.")
}
From 75746e22731b76de0d2066745bcbbe3a52365a8e Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 16:02:22 +0200
Subject: [PATCH 27/34] Docs: regenerate `.Rd` man files
---
man/AsyncBackend.Rd | 36 +++++++-
man/Backend.Rd | 1 +
man/Context.Rd | 36 +++++++-
man/ProgressTrackingContext.Rd | 31 ++++++-
man/Service.Rd | 36 +++++++-
man/SyncBackend.Rd | 36 +++++++-
man/UserApiConsumer.Rd | 156 +++++++++++++++++++++++++++++++++
man/clear.Rd | 2 +-
man/evaluate.Rd | 2 +-
man/export.Rd | 2 +-
man/par_lapply.Rd | 97 ++++++++++++++++++++
man/par_sapply.Rd | 28 ++----
man/parabar-package.Rd | 20 +++--
man/peek.Rd | 2 +-
man/start_backend.Rd | 2 +-
man/stop_backend.Rd | 2 +-
16 files changed, 438 insertions(+), 51 deletions(-)
create mode 100644 man/UserApiConsumer.Rd
create mode 100644 man/par_lapply.Rd
diff --git a/man/AsyncBackend.Rd b/man/AsyncBackend.Rd
index e96cde8..1f0311a 100644
--- a/man/AsyncBackend.Rd
+++ b/man/AsyncBackend.Rd
@@ -124,6 +124,7 @@ has been fetched, the backend is free to deploy another task.
\item \href{#method-AsyncBackend-export}{\code{AsyncBackend$export()}}
\item \href{#method-AsyncBackend-evaluate}{\code{AsyncBackend$evaluate()}}
\item \href{#method-AsyncBackend-sapply}{\code{AsyncBackend$sapply()}}
+\item \href{#method-AsyncBackend-lapply}{\code{AsyncBackend$lapply()}}
\item \href{#method-AsyncBackend-get_output}{\code{AsyncBackend$get_output()}}
\item \href{#method-AsyncBackend-clone}{\code{AsyncBackend$clone()}}
}
@@ -277,8 +278,33 @@ Run a task on the backend akin to \code{\link[parallel:clusterApply]{parallel::p
\subsection{Arguments}{
\if{html}{\out{}}
\describe{
-\item{\code{x}}{A vector (i.e., usually of integers) to pass to the \code{fun}
-function.}
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
+
+\item{\code{fun}}{A function to apply to each element of \code{x}.}
+
+\item{\code{...}}{Additional arguments to pass to the \code{fun} function.}
+}
+\if{html}{\out{
}}
+}
+\subsection{Returns}{
+This method returns void. The output of the task execution must be
+stored in the private field \code{.output} on the \code{\link{Backend}}
+abstract class, and is accessible via the \code{get_output()} method.
+}
+}
+\if{html}{\out{
}}
+\if{html}{\out{}}
+\if{latex}{\out{\hypertarget{method-AsyncBackend-lapply}{}}}
+\subsection{Method \code{lapply()}}{
+Run a task on the backend akin to \code{\link[parallel:clusterApply]{parallel::parLapply()}}.
+\subsection{Usage}{
+\if{html}{\out{}}\preformatted{AsyncBackend$lapply(x, fun, ...)}\if{html}{\out{
}}
+}
+
+\subsection{Arguments}{
+\if{html}{\out{}}
+\describe{
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
\item{\code{fun}}{A function to apply to each element of \code{x}.}
@@ -325,8 +351,10 @@ task is still running.
}
\subsection{Returns}{
-A vector or list of the same length as \code{x} containing the results of
-the \code{fun}. It resembles the format of \code{\link[base:lapply]{base::sapply()}}.
+A vector, matrix, or list of the same length as \code{x}, containing the
+results of the \code{fun}. The output format differs based on the specific
+operation employed. Check out the documentation for the \code{apply}
+operations of \code{\link[parallel:parallel-package]{parallel::parallel}} for more information.
}
}
\if{html}{\out{
}}
diff --git a/man/Backend.Rd b/man/Backend.Rd
index a9e941e..22d4fe2 100644
--- a/man/Backend.Rd
+++ b/man/Backend.Rd
@@ -52,6 +52,7 @@ implementation has an active cluster.}
parabar::Service$evaluate()
parabar::Service$export()
parabar::Service$get_output()
+
parabar::Service$lapply()
parabar::Service$peek()
parabar::Service$sapply()
parabar::Service$start()
diff --git a/man/Context.Rd b/man/Context.Rd
index aa3bf9f..03ff003 100644
--- a/man/Context.Rd
+++ b/man/Context.Rd
@@ -101,6 +101,7 @@ context.}
\item \href{#method-Context-export}{\code{Context$export()}}
\item \href{#method-Context-evaluate}{\code{Context$evaluate()}}
\item \href{#method-Context-sapply}{\code{Context$sapply()}}
+\item \href{#method-Context-lapply}{\code{Context$lapply()}}
\item \href{#method-Context-get_output}{\code{Context$get_output()}}
\item \href{#method-Context-clone}{\code{Context$clone()}}
}
@@ -246,8 +247,33 @@ Run a task on the backend akin to \code{\link[parallel:clusterApply]{parallel::p
\subsection{Arguments}{
\if{html}{\out{
}}
\describe{
-\item{\code{x}}{A vector (i.e., usually of integers) to pass to the \code{fun}
-function.}
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
+
+\item{\code{fun}}{A function to apply to each element of \code{x}.}
+
+\item{\code{...}}{Additional arguments to pass to the \code{fun} function.}
+}
+\if{html}{\out{
}}
+}
+\subsection{Returns}{
+This method returns void. The output of the task execution must be
+stored in the private field \code{.output} on the \code{\link{Backend}}
+abstract class, and is accessible via the \code{get_output()} method.
+}
+}
+\if{html}{\out{
}}
+\if{html}{\out{
}}
+\if{latex}{\out{\hypertarget{method-Context-lapply}{}}}
+\subsection{Method \code{lapply()}}{
+Run a task on the backend akin to \code{\link[parallel:clusterApply]{parallel::parLapply()}}.
+\subsection{Usage}{
+\if{html}{\out{
}}\preformatted{Context$lapply(x, fun, ...)}\if{html}{\out{
}}
+}
+
+\subsection{Arguments}{
+\if{html}{\out{
}}
+\describe{
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
\item{\code{fun}}{A function to apply to each element of \code{x}.}
@@ -288,8 +314,10 @@ task.
}
\subsection{Returns}{
-A vector or list of the same length as \code{x} containing the results of
-the \code{fun}. It resembles the format of \code{\link[base:lapply]{base::sapply()}}.
+A vector, matrix, or list of the same length as \code{x}, containing the
+results of the \code{fun}. The output format differs based on the specific
+operation employed. Check out the documentation for the \code{apply}
+operations of \code{\link[parallel:parallel-package]{parallel::parallel}} for more information.
}
}
\if{html}{\out{
}}
diff --git a/man/ProgressTrackingContext.Rd b/man/ProgressTrackingContext.Rd
index 590c4b0..43710b5 100644
--- a/man/ProgressTrackingContext.Rd
+++ b/man/ProgressTrackingContext.Rd
@@ -127,6 +127,7 @@ context$stop()
\item \href{#method-ProgressTrackingContext-set_bar}{\code{ProgressTrackingContext$set_bar()}}
\item \href{#method-ProgressTrackingContext-configure_bar}{\code{ProgressTrackingContext$configure_bar()}}
\item \href{#method-ProgressTrackingContext-sapply}{\code{ProgressTrackingContext$sapply()}}
+\item \href{#method-ProgressTrackingContext-lapply}{\code{ProgressTrackingContext$lapply()}}
\item \href{#method-ProgressTrackingContext-clone}{\code{ProgressTrackingContext$clone()}}
}
}
@@ -216,8 +217,34 @@ progress bar.
\subsection{Arguments}{
\if{html}{\out{
}}
\describe{
-\item{\code{x}}{A vector (i.e., usually of integers) to pass to the \code{fun}
-function.}
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
+
+\item{\code{fun}}{A function to apply to each element of \code{x}.}
+
+\item{\code{...}}{Additional arguments to pass to the \code{fun} function.}
+}
+\if{html}{\out{
}}
+}
+\subsection{Returns}{
+This method returns void. The output of the task execution must be
+stored in the private field \code{.output} on the \code{\link{Backend}}
+abstract class, and is accessible via the \code{get_output()} method.
+}
+}
+\if{html}{\out{
}}
+\if{html}{\out{
}}
+\if{latex}{\out{\hypertarget{method-ProgressTrackingContext-lapply}{}}}
+\subsection{Method \code{lapply()}}{
+Run a task on the backend akin to \code{\link[parallel:clusterApply]{parallel::parLapply()}}, but with a
+progress bar.
+\subsection{Usage}{
+\if{html}{\out{
}}\preformatted{ProgressTrackingContext$lapply(x, fun, ...)}\if{html}{\out{
}}
+}
+
+\subsection{Arguments}{
+\if{html}{\out{
}}
+\describe{
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
\item{\code{fun}}{A function to apply to each element of \code{x}.}
diff --git a/man/Service.Rd b/man/Service.Rd
index 8b01041..2160b6b 100644
--- a/man/Service.Rd
+++ b/man/Service.Rd
@@ -23,6 +23,7 @@ and \code{\link{Context}}.
\item \href{#method-Service-export}{\code{Service$export()}}
\item \href{#method-Service-evaluate}{\code{Service$evaluate()}}
\item \href{#method-Service-sapply}{\code{Service$sapply()}}
+\item \href{#method-Service-lapply}{\code{Service$lapply()}}
\item \href{#method-Service-get_output}{\code{Service$get_output()}}
\item \href{#method-Service-clone}{\code{Service$clone()}}
}
@@ -167,8 +168,33 @@ Run a task on the backend akin to \code{\link[parallel:clusterApply]{parallel::p
\subsection{Arguments}{
\if{html}{\out{
}}
\describe{
-\item{\code{x}}{A vector (i.e., usually of integers) to pass to the \code{fun}
-function.}
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
+
+\item{\code{fun}}{A function to apply to each element of \code{x}.}
+
+\item{\code{...}}{Additional arguments to pass to the \code{fun} function.}
+}
+\if{html}{\out{
}}
+}
+\subsection{Returns}{
+This method returns void. The output of the task execution must be
+stored in the private field \code{.output} on the \code{\link{Backend}}
+abstract class, and is accessible via the \code{get_output()} method.
+}
+}
+\if{html}{\out{
}}
+\if{html}{\out{
}}
+\if{latex}{\out{\hypertarget{method-Service-lapply}{}}}
+\subsection{Method \code{lapply()}}{
+Run a task on the backend akin to \code{\link[parallel:clusterApply]{parallel::parLapply()}}.
+\subsection{Usage}{
+\if{html}{\out{
}}\preformatted{Service$lapply(x, fun, ...)}\if{html}{\out{
}}
+}
+
+\subsection{Arguments}{
+\if{html}{\out{
}}
+\describe{
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
\item{\code{fun}}{A function to apply to each element of \code{x}.}
@@ -208,8 +234,10 @@ task.
}
\subsection{Returns}{
-A vector or list of the same length as \code{x} containing the results of
-the \code{fun}. It resembles the format of \code{\link[base:lapply]{base::sapply()}}.
+A vector, matrix, or list of the same length as \code{x}, containing the
+results of the \code{fun}. The output format differs based on the specific
+operation employed. Check out the documentation for the \code{apply}
+operations of \code{\link[parallel:parallel-package]{parallel::parallel}} for more information.
}
}
\if{html}{\out{
}}
diff --git a/man/SyncBackend.Rd b/man/SyncBackend.Rd
index 801def4..3de684a 100644
--- a/man/SyncBackend.Rd
+++ b/man/SyncBackend.Rd
@@ -93,6 +93,7 @@ backend$active
\item \href{#method-SyncBackend-export}{\code{SyncBackend$export()}}
\item \href{#method-SyncBackend-evaluate}{\code{SyncBackend$evaluate()}}
\item \href{#method-SyncBackend-sapply}{\code{SyncBackend$sapply()}}
+\item \href{#method-SyncBackend-lapply}{\code{SyncBackend$lapply()}}
\item \href{#method-SyncBackend-get_output}{\code{SyncBackend$get_output()}}
\item \href{#method-SyncBackend-clone}{\code{SyncBackend$clone()}}
}
@@ -246,8 +247,33 @@ Run a task on the backend akin to \code{\link[parallel:clusterApply]{parallel::p
\subsection{Arguments}{
\if{html}{\out{
}}
\describe{
-\item{\code{x}}{A vector (i.e., usually of integers) to pass to the \code{fun}
-function.}
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
+
+\item{\code{fun}}{A function to apply to each element of \code{x}.}
+
+\item{\code{...}}{Additional arguments to pass to the \code{fun} function.}
+}
+\if{html}{\out{
}}
+}
+\subsection{Returns}{
+This method returns void. The output of the task execution must be
+stored in the private field \code{.output} on the \code{\link{Backend}}
+abstract class, and is accessible via the \code{get_output()} method.
+}
+}
+\if{html}{\out{
}}
+\if{html}{\out{
}}
+\if{latex}{\out{\hypertarget{method-SyncBackend-lapply}{}}}
+\subsection{Method \code{lapply()}}{
+Run a task on the backend akin to \code{\link[parallel:clusterApply]{parallel::parLapply()}}.
+\subsection{Usage}{
+\if{html}{\out{
}}\preformatted{SyncBackend$lapply(x, fun, ...)}\if{html}{\out{
}}
+}
+
+\subsection{Arguments}{
+\if{html}{\out{
}}
+\describe{
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
\item{\code{fun}}{A function to apply to each element of \code{x}.}
@@ -279,8 +305,10 @@ task.
}
\subsection{Returns}{
-A vector or list of the same length as \code{x} containing the results of
-the \code{fun}. It resembles the format of \code{\link[base:lapply]{base::sapply()}}.
+A vector, matrix, or list of the same length as \code{x}, containing the
+results of the \code{fun}. The output format differs based on the specific
+operation employed. Check out the documentation for the \code{apply}
+operations of \code{\link[parallel:parallel-package]{parallel::parallel}} for more information.
}
}
\if{html}{\out{
}}
diff --git a/man/UserApiConsumer.Rd b/man/UserApiConsumer.Rd
new file mode 100644
index 0000000..808780d
--- /dev/null
+++ b/man/UserApiConsumer.Rd
@@ -0,0 +1,156 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/UserApiConsumer.R
+\name{UserApiConsumer}
+\alias{UserApiConsumer}
+\title{UserApiConsumer}
+\description{
+This class is an opinionated interface around the developer API of the
+\code{\link{parabar}} package. See the \strong{Details} section for more
+information on how this class works.
+}
+\details{
+This class acts as a wrapper around the \code{\link[R6:R6Class]{R6::R6}} developer API of the
+\code{\link{parabar}} package. In a nutshell, it provides an opinionated
+interface by wrapping the developer API in simple functional calls. More
+specifically, for executing a task in parallel, this class performs the
+following steps:
+\itemize{
+\item Validates the backend provided.
+\item Instantiates an appropriate \code{\link{parabar}} context based on the
+backend. If the backend supports progress tracking (i.e., the backend is an
+instance of \code{\link{AsyncBackend}}), a progress tracking context (i.e.,
+\code{\link{ProgressTrackingContext}}) is instantiated and used. Otherwise,
+a regular context (i.e., \code{\link{Context}}) is instantiated. A regular
+context is also used if the progress tracking is disabled via the
+\code{\link{Options}} instance.
+\item Registers the \code{\link[=Backend]{backend}} with the context.
+\item Instantiates and configures the progress bar based on the
+\code{\link{Options}} instance in the session \code{\link[base:options]{base::.Options}} list.
+\item Executes the task in parallel, and displays a progress bar if appropriate.
+\item Fetches the results from the backend and returns them.
+}
+}
+\examples{
+# Define a simple task.
+task <- function(x) {
+ # Perform computations.
+ Sys.sleep(0.01)
+
+ # Return the result.
+ return(x + 1)
+}
+
+# Start an asynchronous backend.
+backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async")
+
+# Change the progress bar options.
+configure_bar(type = "modern", format = "[:bar] :percent")
+
+# Create an user API consumer.
+consumer <- UserApiConsumer$new()
+
+# Execute the task using the `sapply` parallel operation.
+output_sapply <- consumer$sapply(backend = backend, x = 1:200, fun = task)
+
+# Print the head of the `sapply` operation output.
+head(output_sapply)
+
+# Execute the task using the `sapply` parallel operation.
+output_lapply <- consumer$lapply(backend = backend, x = 1:200, fun = task)
+
+# Print the head of the `lapply` operation output.
+head(output_lapply)
+
+# Stop the backend.
+stop_backend(backend)
+
+}
+\seealso{
+\code{\link[=start_backend]{start_backend()}}, \code{\link[=stop_backend]{stop_backend()}},
+\code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}}, and
+\code{\link[=par_lapply]{par_lapply()}}.
+}
+\section{Methods}{
+\subsection{Public methods}{
+\itemize{
+\item \href{#method-UserApiConsumer-sapply}{\code{UserApiConsumer$sapply()}}
+\item \href{#method-UserApiConsumer-lapply}{\code{UserApiConsumer$lapply()}}
+\item \href{#method-UserApiConsumer-clone}{\code{UserApiConsumer$clone()}}
+}
+}
+\if{html}{\out{
}}
+\if{html}{\out{
}}
+\if{latex}{\out{\hypertarget{method-UserApiConsumer-sapply}{}}}
+\subsection{Method \code{sapply()}}{
+Execute a task in parallel akin to \code{\link[parallel:clusterApply]{parallel::parSapply()}}.
+\subsection{Usage}{
+\if{html}{\out{
}}\preformatted{UserApiConsumer$sapply(backend, x, fun, ...)}\if{html}{\out{
}}
+}
+
+\subsection{Arguments}{
+\if{html}{\out{
}}
+\describe{
+\item{\code{backend}}{An object of class \code{\link{Backend}} as returned by
+the \code{\link[=start_backend]{start_backend()}} function. It can also be \code{NULL} to run
+the task sequentially via \code{\link[base:lapply]{base::sapply()}}.}
+
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
+
+\item{\code{fun}}{A function to apply to each element of \code{x}.}
+
+\item{\code{...}}{Additional arguments to pass to the \code{fun} function.}
+}
+\if{html}{\out{
}}
+}
+\subsection{Returns}{
+A vector of the same length as \code{x} containing the results of the
+\code{fun}. The output format resembles that of \code{\link[base:lapply]{base::sapply()}}.
+}
+}
+\if{html}{\out{
}}
+\if{html}{\out{
}}
+\if{latex}{\out{\hypertarget{method-UserApiConsumer-lapply}{}}}
+\subsection{Method \code{lapply()}}{
+Execute a task in parallel akin to \code{\link[parallel:clusterApply]{parallel::parLapply()}}.
+\subsection{Usage}{
+\if{html}{\out{
}}\preformatted{UserApiConsumer$lapply(backend, x, fun, ...)}\if{html}{\out{
}}
+}
+
+\subsection{Arguments}{
+\if{html}{\out{
}}
+\describe{
+\item{\code{backend}}{An object of class \code{\link{Backend}} as returned by
+the \code{\link[=start_backend]{start_backend()}} function. It can also be \code{NULL} to run
+the task sequentially via \code{\link[base:lapply]{base::lapply()}}.}
+
+\item{\code{x}}{An atomic vector or list to pass to the \code{fun} function.}
+
+\item{\code{fun}}{A function to apply to each element of \code{x}.}
+
+\item{\code{...}}{Additional arguments to pass to the \code{fun} function.}
+}
+\if{html}{\out{
}}
+}
+\subsection{Returns}{
+A list of the same length as \code{x} containing the results of the \code{fun}.
+The output format resembles that of \code{\link[base:lapply]{base::lapply()}}.
+}
+}
+\if{html}{\out{
}}
+\if{html}{\out{
}}
+\if{latex}{\out{\hypertarget{method-UserApiConsumer-clone}{}}}
+\subsection{Method \code{clone()}}{
+The objects of this class are cloneable with this method.
+\subsection{Usage}{
+\if{html}{\out{
}}\preformatted{UserApiConsumer$clone(deep = FALSE)}\if{html}{\out{
}}
+}
+
+\subsection{Arguments}{
+\if{html}{\out{
}}
+\describe{
+\item{\code{deep}}{Whether to make a deep clone.}
+}
+\if{html}{\out{
}}
+}
+}
+}
diff --git a/man/clear.Rd b/man/clear.Rd
index e3cf463..115b4a2 100644
--- a/man/clear.Rd
+++ b/man/clear.Rd
@@ -84,5 +84,5 @@ backend$active
\seealso{
\code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}},
\code{\link[=evaluate]{evaluate()}}, \code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}},
-\code{\link[=stop_backend]{stop_backend()}}, and \code{\link{Service}}.
+\code{\link[=par_lapply]{par_lapply()}}, \code{\link[=stop_backend]{stop_backend()}}, and \code{\link{Service}}.
}
diff --git a/man/evaluate.Rd b/man/evaluate.Rd
index 2678c0c..14e643d 100644
--- a/man/evaluate.Rd
+++ b/man/evaluate.Rd
@@ -87,5 +87,5 @@ backend$active
\seealso{
\code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}},
\code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}},
-\code{\link[=stop_backend]{stop_backend()}}, and \code{\link{Service}}.
+\code{\link[=par_lapply]{par_lapply()}}, \code{\link[=stop_backend]{stop_backend()}}, and \code{\link{Service}}.
}
diff --git a/man/export.Rd b/man/export.Rd
index 3ccf104..afda767 100644
--- a/man/export.Rd
+++ b/man/export.Rd
@@ -90,5 +90,5 @@ backend$active
\seealso{
\code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=evaluate]{evaluate()}},
\code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}},
-\code{\link[=stop_backend]{stop_backend()}}, and \code{\link{Service}}.
+\code{\link[=par_lapply]{par_lapply()}}, \code{\link[=stop_backend]{stop_backend()}}, and \code{\link{Service}}.
}
diff --git a/man/par_lapply.Rd b/man/par_lapply.Rd
new file mode 100644
index 0000000..ec3d248
--- /dev/null
+++ b/man/par_lapply.Rd
@@ -0,0 +1,97 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/exports.r
+\name{par_lapply}
+\alias{par_lapply}
+\title{Run a Task in Parallel}
+\usage{
+par_lapply(backend = NULL, x, fun, ...)
+}
+\arguments{
+\item{backend}{An object of class \code{\link{Backend}} as returned by the
+\code{\link[=start_backend]{start_backend()}} function. It can also be \code{NULL} to run the task
+sequentially via \code{\link[base:lapply]{base::lapply()}}. The default value is \code{NULL}.}
+
+\item{x}{An atomic vector or list to pass to the \code{fun} function.}
+
+\item{fun}{A function to apply to each element of \code{x}.}
+
+\item{...}{Additional arguments to pass to the \code{fun} function.}
+}
+\value{
+A list of the same length as \code{x} containing the results of the \code{fun}. The
+output format resembles that of \code{\link[base:lapply]{base::lapply()}}.
+}
+\description{
+This function can be used to run a task in parallel. The task is executed in
+parallel on the specified backend, similar to \code{\link[parallel:clusterApply]{parallel::parLapply()}}. If
+\code{backend = NULL}, the task is executed sequentially using \code{\link[base:lapply]{base::lapply()}}.
+See the \strong{Details} section for more information on how this function works.
+}
+\details{
+This function uses the \code{\link{UserApiConsumer}} class that acts like an
+interface for the developer API of the \code{\link{parabar}} package.
+}
+\examples{
+\donttest{
+
+# Define a simple task.
+task <- function(x) {
+ # Perform computations.
+ Sys.sleep(0.01)
+
+ # Return the result.
+ return(x + 1)
+}
+
+# Start an asynchronous backend.
+backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "async")
+
+# Run a task in parallel.
+results <- par_lapply(backend, x = 1:300, fun = task)
+
+# Disable progress tracking.
+set_option("progress_track", FALSE)
+
+# Run a task in parallel.
+results <- par_lapply(backend, x = 1:300, fun = task)
+
+# Enable progress tracking.
+set_option("progress_track", TRUE)
+
+# Change the progress bar options.
+configure_bar(type = "modern", format = "[:bar] :percent")
+
+# Run a task in parallel.
+results <- par_lapply(backend, x = 1:300, fun = task)
+
+# Stop the backend.
+stop_backend(backend)
+
+# Start a synchronous backend.
+backend <- start_backend(cores = 2, cluster_type = "psock", backend_type = "sync")
+
+# Run a task in parallel.
+results <- par_lapply(backend, x = 1:300, fun = task)
+
+# Disable progress tracking to remove the warning that progress is not supported.
+set_option("progress_track", FALSE)
+
+# Run a task in parallel.
+results <- par_lapply(backend, x = 1:300, fun = task)
+
+# Stop the backend.
+stop_backend(backend)
+
+# Run the task using the `base::lapply` (i.e., non-parallel).
+results <- par_lapply(NULL, x = 1:300, fun = task)
+
+}
+
+}
+\seealso{
+\code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}},
+\code{\link[=evaluate]{evaluate()}}, \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}},
+\code{\link[=par_sapply]{par_sapply()}}, \code{\link[=stop_backend]{stop_backend()}}, \code{\link[=set_option]{set_option()}},
+\code{\link[=get_option]{get_option()}}, \code{\link{Options}}, \code{\link{UserApiConsumer}},
+and \code{\link{Service}}.
+}
diff --git a/man/par_sapply.Rd b/man/par_sapply.Rd
index ea6bafb..3dc5cbd 100644
--- a/man/par_sapply.Rd
+++ b/man/par_sapply.Rd
@@ -11,15 +11,15 @@ par_sapply(backend = NULL, x, fun, ...)
\code{\link[=start_backend]{start_backend()}} function. It can also be \code{NULL} to run the task
sequentially via \code{\link[base:lapply]{base::sapply()}}. The default value is \code{NULL}.}
-\item{x}{A vector (i.e., usually of integers) to pass to the \code{fun} function.}
+\item{x}{An atomic vector or list to pass to the \code{fun} function.}
\item{fun}{A function to apply to each element of \code{x}.}
\item{...}{Additional arguments to pass to the \code{fun} function.}
}
\value{
-A vector or list of the same length as \code{x} containing the results of the
-\code{fun}. The output format resembles that of \code{\link[base:lapply]{base::sapply()}}.
+A vector of the same length as \code{x} containing the results of the \code{fun}. The
+output format resembles that of \code{\link[base:lapply]{base::sapply()}}.
}
\description{
This function can be used to run a task in parallel. The task is executed in
@@ -28,21 +28,8 @@ parallel on the specified backend, similar to \code{\link[parallel:clusterApply]
See the \strong{Details} section for more information on how this function works.
}
\details{
-This function is a wrapper around the developer API of the
-\code{\link{parabar}} package. More specifically, this function:
-\itemize{
-\item Instantiates an appropriate \code{\link{parabar}} context. If the backend
-supports progress tracking (i.e., the backend is an instance of
-\code{\link{AsyncBackend}}), a progress tracking context (i.e.,
-\code{\link{ProgressTrackingContext}}) is instantiated and used. Otherwise, a
-regular context (i.e., \code{\link{Context}}) is instantiated. A regular
-context is also used if the progress tracking is disabled via the
-\code{\link{Options}} instance.
-\item Registers the \code{\link[=Backend]{backend}} with the context.
-\item Instantiates and configures the progress bar based on the
-\code{\link{Options}} instance in the session \code{\link[base:options]{base::.Options}} list.
-\item Executes the task in parallel, and displays a progress bar if appropriate.
-}
+This function uses the \code{\link{UserApiConsumer}} class that acts like an
+interface for the developer API of the \code{\link{parabar}} package.
}
\examples{
\donttest{
@@ -104,6 +91,7 @@ results <- par_sapply(NULL, x = 1:300, fun = task)
\seealso{
\code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}},
\code{\link[=evaluate]{evaluate()}}, \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}},
-\code{\link[=stop_backend]{stop_backend()}}, \code{\link[=set_option]{set_option()}}, \code{\link[=get_option]{get_option()}},
-\code{\link{Options}}, and \code{\link{Service}}.
+\code{\link[=par_lapply]{par_lapply()}}, \code{\link[=stop_backend]{stop_backend()}}, \code{\link[=set_option]{set_option()}},
+\code{\link[=get_option]{get_option()}}, \code{\link{Options}}, \code{\link{UserApiConsumer}},
+and \code{\link{Service}}.
}
diff --git a/man/parabar-package.Rd b/man/parabar-package.Rd
index 945ee09..5447e12 100644
--- a/man/parabar-package.Rd
+++ b/man/parabar-package.Rd
@@ -19,7 +19,7 @@ solution for parallel processing in their packages.
}
\section{Users}{
-For the first category of users, \code{\link{parabar}} provides seven main
+For the first category of users, \code{\link{parabar}} provides several main
functions of interest:
\itemize{
\item \code{\link[=start_backend]{start_backend()}}: creates a parallel backend for executing tasks
@@ -30,6 +30,10 @@ eligible for garbage collection.
\code{\link[base:lapply]{base::sapply()}} function when no backend is provided. However, when a
backend is provided, the function will execute a task in parallel on the
backend, similar to the built-in function \code{\link[parallel:clusterApply]{parallel::parSapply()}}.
+\item \code{\link[=par_lapply]{par_lapply()}}: is a drop-in replacement for the built-in
+\code{\link[base:lapply]{base::lapply()}} function when no backend is provided. However, when a
+backend is provided, the function will execute a task in parallel on the
+backend, similar to the built-in function \code{\link[parallel:clusterApply]{parallel::parLapply()}}.
\item \code{\link[=clear]{clear()}}: removes all variables available on a backend.
\item \code{\link[=peek]{peek()}}: returns the names of all variables available on a
backend.
@@ -72,7 +76,8 @@ The \code{\link{Service}} interface defines the following operations:
\code{\link[=Service]{start()}}, \code{\link[=Service]{stop()}},
\code{\link[=Service]{clear()}}, \code{\link[=Service]{peek()}},
\code{\link[=Service]{export()}}, \code{\link[=Service]{evaluate()}},
-\code{\link[=Service]{sapply()}}, and \code{\link[=Service]{get_output()}}.
+\code{\link[=Service]{sapply()}}, \code{\link[=Service]{lapply()}}, and
+\code{\link[=Service]{get_output()}}.
Check out the documentation for \code{\link{Service}} for more information on
each method.
@@ -85,9 +90,9 @@ operates. The default, regular \code{\link{Context}} class simply forwards the
call to the corresponding backend method. However, a more complex context can
augment the operation before forwarding the call to the backend. One example
of a complex context is the \code{\link{ProgressTrackingContext}} class. This
-class extends the regular \code{\link{Context}} class and decorates the
-backend \code{\link[=Service]{sapply()}} operation to log the progress after
-each task execution and display a progress bar.
+class extends the regular \code{\link{Context}} class and decorates, for
+example, the backend \code{\link[=Service]{sapply()}} operation to log the
+progress after each task execution and display a progress bar.
The following are the main classes provided by \code{parabar}:
\itemize{
@@ -105,7 +110,8 @@ asynchronous backend.
\item \code{\link{BackendFactory}}: factory for creating backend objects.
\item \code{\link{Context}}: default context for executing backend operations.
\item \code{\link{ProgressTrackingContext}}: context for decorating the
-\code{\link[=Service]{sapply()}} operation to track and display progress.
+\code{\link[=Service]{sapply()}} and \code{\link[=Service]{lapply()}}
+operations to track and display the execution progress.
\item \code{\link{ContextFactory}}: factory for creating context objects.
}
}
@@ -121,7 +127,7 @@ implemented by concrete bar classes.
\item \code{\link{BasicBar}}: a simple, but robust, bar created via
\code{\link[utils:txtProgressBar]{utils::txtProgressBar()}} extending the \code{\link{Bar}} abstract class.
\item \code{\link{ModernBar}}: a modern bar created via \code{\link[progress:progress_bar]{progress::progress_bar}}
-extending the \link{Bar} abstract class.
+extending the \code{\link{Bar}} abstract class.
\item \code{\link{BarFactory}}: factory for creating bar objects.
}
diff --git a/man/peek.Rd b/man/peek.Rd
index 2a07a22..36dad9f 100644
--- a/man/peek.Rd
+++ b/man/peek.Rd
@@ -86,5 +86,5 @@ backend$active
\seealso{
\code{\link[=start_backend]{start_backend()}}, \code{\link[=export]{export()}}, \code{\link[=evaluate]{evaluate()}},
\code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}},
-\code{\link[=stop_backend]{stop_backend()}}, and \code{\link{Service}}.
+\code{\link[=par_lapply]{par_lapply()}}, \code{\link[=stop_backend]{stop_backend()}}, and \code{\link{Service}}.
}
diff --git a/man/start_backend.Rd b/man/start_backend.Rd
index 16bd87c..cd7eb0f 100644
--- a/man/start_backend.Rd
+++ b/man/start_backend.Rd
@@ -130,5 +130,5 @@ backend$active
\seealso{
\code{\link[=peek]{peek()}}, \code{\link[=export]{export()}}, \code{\link[=evaluate]{evaluate()}},
\code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}}, \code{\link[=par_sapply]{par_sapply()}},
-\code{\link[=stop_backend]{stop_backend()}}, and \code{\link{Service}}.
+\code{\link[=par_lapply]{par_lapply()}}, \code{\link[=stop_backend]{stop_backend()}}, and \code{\link{Service}}.
}
diff --git a/man/stop_backend.Rd b/man/stop_backend.Rd
index 574bcc2..e36356f 100644
--- a/man/stop_backend.Rd
+++ b/man/stop_backend.Rd
@@ -89,5 +89,5 @@ backend$active
\seealso{
\code{\link[=start_backend]{start_backend()}}, \code{\link[=peek]{peek()}}, \code{\link[=export]{export()}},
\code{\link[=evaluate]{evaluate()}}, \code{\link[=clear]{clear()}}, \code{\link[=configure_bar]{configure_bar()}},
-\code{\link[=par_sapply]{par_sapply()}}, and \code{\link{Service}}.
+\code{\link[=par_sapply]{par_sapply()}}, \code{\link[=par_lapply]{par_lapply()}}, and \code{\link{Service}}.
}
From 7dd66a846e5b9ed4dcd05cba5b45e25a40459e80 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 16:07:59 +0200
Subject: [PATCH 28/34] Docs: add missing topics to `_pkgdown.yml`
---
_pkgdown.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/_pkgdown.yml b/_pkgdown.yml
index 85dd21d..1eddfcc 100644
--- a/_pkgdown.yml
+++ b/_pkgdown.yml
@@ -13,6 +13,7 @@ reference:
- clear
- evaluate
- par_sapply
+ - par_lapply
- configure_bar
- matches("get_|set_")
- subtitle: Developer Classes
@@ -36,6 +37,7 @@ reference:
- Helper
- Warning
- Exception
+ - UserApiConsumer
- subtitle: Miscellaneous
desc: Very specific functions that may safely be ignored.
- contents:
From 49ddf1ebe5536c5aed44beb8a423ba45c3bc5108 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 16:24:40 +0200
Subject: [PATCH 29/34] Docs: add changes to `NEWS`
---
NEWS.md | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/NEWS.md b/NEWS.md
index fe0302b..1ab4fe4 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,24 @@
+# Development
+
+## Added
+- Add more tests to improve coverage.
+- Add `par_lapply` function to the user `API`. The `par_lapply` function can be
+ used to run tasks in parallel akin to `parallel::parLapply`.
+- Add `UserApiConsumer` `R6` class that provides an opinionated wrapper around
+ the developer `API` of the `parabar` package. All parallel operations (e.g.,
+ `par_sapply` and `par_lapply`) follow more or less the same pattern. The
+ `UserApiConsumer` encapsulates this pattern and makes it easier to extend
+ `parabar` with new parallel functions (e.g., `par_apply`) while avoiding code
+ duplication. The `UserApiConsumer` class can also be used as a standalone
+ class for parallel operations, however, its primary purpose is to be used by
+ the parallel task execution functions in the user `API`.
+
+## Changed
+- Refactor test helpers to avoid code duplication.
+- Update `par_sapply` to use the `UserApiConsumer` class.
+- Update the developer `API` `R6` classes to implement the `lapply` parallel
+ operation.
+
# parabar 1.0.3
## Fixed
From 26f1f8a9197a9c64deb7c645fe4bc49edb5d0b12 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 16:25:12 +0200
Subject: [PATCH 30/34] Ci: update line exclusions for coverage workflow
---
.github/workflows/test-coverage.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml
index 1e7946d..223206c 100644
--- a/.github/workflows/test-coverage.yaml
+++ b/.github/workflows/test-coverage.yaml
@@ -43,5 +43,5 @@ jobs:
- name: Test coverage
env:
PROCESSX_NOTIFY_OLD_SIGCHLD: true
- run: covr::codecov(line_exclusions = list("R/parabar-package.R"))
+ run: covr::codecov(line_exclusions = list("R/parabar-package.R", "R/UserApiConsumer.R" = c(59:77)))
shell: Rscript {0}
From 34f184559aa498d51d4416887749cdb43778b386 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 16:28:37 +0200
Subject: [PATCH 31/34] Fix: update `export` operation to default to parent
frame
---
R/Context.R | 9 +++++++--
R/SyncBackend.R | 6 +++---
man/Context.Rd | 2 +-
man/SyncBackend.Rd | 2 +-
4 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/R/Context.R b/R/Context.R
index 82e31c2..4b0687f 100644
--- a/R/Context.R
+++ b/R/Context.R
@@ -155,12 +155,17 @@ Context <- R6::R6Class("Context",
#' @param variables A character vector of variable names to export.
#'
#' @param environment An environment object from which to export the
- #' variables.
+ #' variables. Defaults to the parent frame.
#'
#' @return This method returns void.
export = function(variables, environment) {
+ # If no environment is provided.
+ if (missing(environment)) {
+ # Use the caller's environment where the variables are defined.
+ environment <- parent.frame()
+ }
+
# Consume the backend API.
- # TODO: Check that this works as expected (i.e., the environment).
private$.backend$export(variables, environment)
},
diff --git a/R/SyncBackend.R b/R/SyncBackend.R
index 6b67518..c29227c 100644
--- a/R/SyncBackend.R
+++ b/R/SyncBackend.R
@@ -244,14 +244,14 @@ SyncBackend <- R6::R6Class("SyncBackend",
#' @param variables A character vector of variable names to export.
#'
#' @param environment An environment object from which to export the
- #' variables.
+ #' variables. Defaults to the parent frame.
#'
#' @return This method returns void.
export = function(variables, environment) {
# If no environment is provided.
if (missing(environment)) {
- # Use the global environment.
- environment <- .GlobalEnv
+ # Use the caller's environment where the variables are defined.
+ environment <- parent.frame()
}
# Export and return the output.
diff --git a/man/Context.Rd b/man/Context.Rd
index 03ff003..9d5945b 100644
--- a/man/Context.Rd
+++ b/man/Context.Rd
@@ -207,7 +207,7 @@ Export variables from a given environment to the backend.
\item{\code{variables}}{A character vector of variable names to export.}
\item{\code{environment}}{An environment object from which to export the
-variables.}
+variables. Defaults to the parent frame.}
}
\if{html}{\out{}}
}
diff --git a/man/SyncBackend.Rd b/man/SyncBackend.Rd
index 3de684a..2e55fef 100644
--- a/man/SyncBackend.Rd
+++ b/man/SyncBackend.Rd
@@ -207,7 +207,7 @@ Export variables from a given environment to the backend.
\item{\code{variables}}{A character vector of variable names to export.}
\item{\code{environment}}{An environment object from which to export the
-variables.}
+variables. Defaults to the parent frame.}
}
\if{html}{\out{
}}
}
From 06d9e6b0bad80211bf1ae6760384ae713d14c1de Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 16:29:28 +0200
Subject: [PATCH 32/34] Fix: disable `file.create` warnings in
`ProgressTrackingContext`
---
R/ProgressTrackingContext.R | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/R/ProgressTrackingContext.R b/R/ProgressTrackingContext.R
index 9096759..be2100c 100644
--- a/R/ProgressTrackingContext.R
+++ b/R/ProgressTrackingContext.R
@@ -126,7 +126,7 @@ ProgressTrackingContext <- R6::R6Class("ProgressTrackingContext",
file_path <- Helper$get_option("progress_log_path")
# Create the temporary file.
- creation_status <- file.create(file_path)
+ creation_status <- file.create(file_path, showWarnings = FALSE)
# If the file creation failed.
if (!creation_status) {
From 3cf757b9d45531bb0083bfc9963cb83849170002 Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 16:30:22 +0200
Subject: [PATCH 33/34] Feat: add `...` for `get_output` in `SyncBackend`
---
R/SyncBackend.R | 4 +++-
man/SyncBackend.Rd | 9 ++++++++-
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/R/SyncBackend.R b/R/SyncBackend.R
index c29227c..7495632 100644
--- a/R/SyncBackend.R
+++ b/R/SyncBackend.R
@@ -313,6 +313,8 @@ SyncBackend <- R6::R6Class("SyncBackend",
#' @description
#' Get the output of the task execution.
#'
+ #' @param ... Additional arguments currently not in use.
+ #'
#' @details
#' This method fetches the output of the task execution after calling
#' the `sapply()` method. It returns the output and immediately removes
@@ -325,7 +327,7 @@ SyncBackend <- R6::R6Class("SyncBackend",
#' results of the `fun`. The output format differs based on the specific
#' operation employed. Check out the documentation for the `apply`
#' operations of [`parallel::parallel`] for more information.
- get_output = function() {
+ get_output = function(...) {
# Reset the output on exit.
on.exit({
# Clear.
diff --git a/man/SyncBackend.Rd b/man/SyncBackend.Rd
index 2e55fef..73b2a16 100644
--- a/man/SyncBackend.Rd
+++ b/man/SyncBackend.Rd
@@ -293,9 +293,16 @@ abstract class, and is accessible via the \code{get_output()} method.
\subsection{Method \code{get_output()}}{
Get the output of the task execution.
\subsection{Usage}{
-\if{html}{\out{}}\preformatted{SyncBackend$get_output()}\if{html}{\out{
}}
+\if{html}{\out{}}\preformatted{SyncBackend$get_output(...)}\if{html}{\out{
}}
}
+\subsection{Arguments}{
+\if{html}{\out{}}
+\describe{
+\item{\code{...}}{Additional arguments currently not in use.}
+}
+\if{html}{\out{
}}
+}
\subsection{Details}{
This method fetches the output of the task execution after calling
the \code{sapply()} method. It returns the output and immediately removes
From e02619ec3950b9910c6a91c9d6efe0727093059a Mon Sep 17 00:00:00 2001
From: mihaiconstantin
Date: Wed, 3 May 2023 16:35:13 +0200
Subject: [PATCH 34/34] Docs: add chanes to `NEWS`
---
NEWS.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/NEWS.md b/NEWS.md
index 1ab4fe4..ef54966 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,8 @@
# Development
## Added
+- Add optional arguments to the `get_output` operation of `SyncBackend` for
+ consistency.
- Add more tests to improve coverage.
- Add `par_lapply` function to the user `API`. The `par_lapply` function can be
used to run tasks in parallel akin to `parallel::parLapply`.
@@ -14,11 +16,18 @@
the parallel task execution functions in the user `API`.
## Changed
+- Disable warnings for `file.create` in `ProgressTrackingContext` class. This
+ warning is superfluous since the code handles creation failures.
- Refactor test helpers to avoid code duplication.
- Update `par_sapply` to use the `UserApiConsumer` class.
- Update the developer `API` `R6` classes to implement the `lapply` parallel
operation.
+## Fixed
+- Fix the `export` operation in the `SyncBackend` and `Context` classes to
+ fallback to the parent environment if the argument `environment` is not
+ provided.
+
# parabar 1.0.3
## Fixed