futureverse / progressr

三 R package: An Inclusive, Unifying API for Progress Updates
https://progressr.futureverse.org
281 stars 12 forks source link

Detect registered progress handler #155

Open martinju opened 1 year ago

martinju commented 1 year ago

Hi

Say I have a function foo() which contains code displaying a progress bar with progressr. Is there a simple way to detect whether either a global handler is registered in the current session, or whether foo() is wrapped within with_progress such that foo() will display a progress bar in my console?

Thanks!

HenrikBengtsson commented 1 year ago

Hello.

Is there a simple way to detect whether either a global handler is registered in the current session,

handlers(global = NA) will return TRUE if there's one registered, otherwise FALSE.

or whether foo() is wrapped within with_progress

No, I don't think that's possible. To generalize your question, what you're after is a way to see if it's possible to detect, from foo(), that there is a handler for conditions of class some_handler the following call:

withCallingHandlers({
  foo()
}, some_handler = function(cond) {
  ...
})

I don't think that's possible in R, but I'm not 100% sure. If you could, please reach out on R-devel and ask about this - it could be an interesting feature to have in R.

In progressr, I could of course have with_progress() to temporarily set an internal flag, or an R option, that can be queried, e.g. has_progressr_handlers(). But, before thinking about that ...

such that foo() will display a progress bar in my console?

What are you really trying to achieve here? Note, even if you could know that there are progression handlers actively "listening", you would not know how they report on the progress. For example, handlers("beep") would not "display a progress bar in my console". There are several other handlers that render progress elsewhere (and more to come).

martinju commented 1 year ago

handlers(global = NA) will return TRUE if there's one registered, otherwise FALSE.

Thanks, that solves my problem assuming everyone uses the global option (which may not be a reasonable assumption).

If you could, please reach out on R-devel and ask about this - it could be an interesting feature to have in R.

I'll try to see if I can make that happen.

In progressr, I could of course have with_progress() to temporarily set an internal flag, or an R option, that can be queried, e.g. has_progressr_handlers(). But, before thinking about that ...

I think that would be very useful for my use case, see below.

What are you really trying to achieve here?

Short story: I want to set different default behavior in a function depending on whether progress handler is enabled or not.

Long story: My use case is a function that splits a potentially memory hungry task into smaller subtasks which are handled separately, and progress is reported for every completed subtask via progressr. The task splitting reduces the memory consumption significantly, at the cost of an increase in the computation time.

With smaller dimensional data input to the function, memory consumption is not an issue, but the computation time still increases. These cases can be identified based on the input. In these cases, the only reason for doing the splitting is if the user wants to display the computation progress. If the user has not registered a progress handler, it would be preferable to not split the tasks to save some computation time.

So, if I could detect whether the user has enabled progress reporting (of any kind), I can set the default behavior to do task splitting when it is enabled, and disable the task splitting if progress reporting is not enabled by the user.

HenrikBengtsson commented 1 year ago

So, if I could detect whether the user has enabled progress reporting (of any kind), I can set the default behavior to do task splitting when it is enabled, and disable the task splitting if progress reporting is not enabled by the user.

I strongly recommend against this. The user might have enabled the global progress handler for other reasons that are unrelated to your package and your function. It's important to always remember that our packages and functions can be used in contexts that we as developers cannot predict or know why and when. Some developers use similar things like if (requireNamespace("foo")) { do this } else { otherwise that }. That introduces a non-deterministic behavior and a major surprise for the end-user; that foo package might have been installed for other reasons.

martinju commented 1 year ago

I highly value your recommendations on this. However, I don't quite understand the big issue with my suggested approach. Based on the input arguments to foo(), I am already setting default values for internal parameters related to computation speed/memory usage (within the function call to foo() only). The output of the function foo() is exactly the same regardless of how I set these internal parameters.

If a progress handler is enabled, the 'visual behavior' of the call to foo() changes since a progress bar is displayed (or rendered elsewhere). Now, I also want the computation speed/memory usage (of the call to foo() only) to depend on whether the progress handler is enabled.

The behavior of other functions, packages and global parameters are not changed depending on whether the package with the function foo() is installed/loaded or not.

It would be very helpful if you could elaborate on your view related to this.