Inspired by this awesome post I felt like playing around with condition handling a bit more.
I think I got it all figured out for warning and error (didn't check message in detail yet), but out of pure interest at this point I wondered
How the same paradigm could be used for alternative conditions (i.e. conditions simply inheriting from
condition, notmessage,warningorerror)When, if at all, the use of such alternative conditions would make any sense in the first place (and/or when
signalCondition()would actually be used)How I can get a custom restart handler to "go back to the environment where the actual expression was evaluated" so it can "dynamically" work with/modify the original expression(s) (in frames further up the calling stack) in order to accomplish full flexibilty for condition handling
Example
Default condition constructor:
condition <- function(subclass, message, call = sys.call(-1), ...) {
structure(
class = c(subclass, "condition"),
list(message = message, call = call),
...
)
}
Custom alternative condition constructor:
custom_condition <- function(subclass, message, call = sys.call(-1), ...) {
cond <- condition(subclass, message, call = call, ...)
message(cond)
}
Example function that issues two alternative conditions at some point:
foo <- function() {
for (ii in 1:3) {
message(paste0("Run #: ", ii))
if (ii == 1) {
custom_condition(subclass="conditionFooExample",
message="I'm just an example condition")
} else if (ii == 3) {
custom_condition(subclass="conditionFooExample2",
message="I'm just another example condition")
}
ii
}
}
Running without any wrappers such as tryCatch(), withCallingHandlers(), withRestarts() or the like:
foo()
Run #: 1
I'm just an example condition
Run #: 2
Run #: 3
I'm just another example condition
As I see it, not really useful as I would have gotten the same result when simply issuing the messages directly via message() in foo() instead of creating customs conditions via custom_condition().
This happens when wrapping it with tryCatch():
tryCatch(
foo(),
conditionFooExample=function(cond) {
cond
},
conditionFooExample2=function(cond) {
cond
}
)
Run #: 1
<conditionFooExample in foo(): I'm just an example condition>
As we see, the first condition leads to kind of a "graceful exit". As I see it, as is right now, not really useful either because it resembles the behavior of warnings.
So I thought if alternative conditions are to be useful at all, then this whole process would probably need to involve custom condition handlers (e.g. for logging purposes: "write something back to a DB and continue") and something similar to invokeRestart("muffleWarning") (see ?condition and look for muffleWarning) in order to "really continue" (as opposed to "gracefully exiting").
So I gues this would then look something like this (PSEUDO CODE)?
withRestarts(
tryCatch(
foo(),
conditionFooExample=function(cond) {
cond ## possibly 'message(cond)' instead?
## Do something meaningful before invoking a restart
invokeRestart("conditionFooExample")
},
conditionFooExample2=function(cond) {
cond ## possibly 'message(cond)' instead?
## Do something meaningful before invoking a restart
invokeRestart("conditionFooExample2")
}
),
conditionFooExample=function() {
## (Maybe) do something more before continuing
## Then continue:
invokeRestart("muffleCondition")
},
conditionFooExample2=function() {
## (Maybe) do something more before continuing
## Then continue:
invokeRestart("muffleCondition")
}
)
Of course, there is no such thing as invokeRestart("muffleCondition") and I'm also not sure if this "architecture" would make any sense.
However, this is where question 3 came up: how do these restart handlers actually work, i.e. how do they get from their frame "one frame up" again?
Last not least I asked myself what would happened if I used signalCondition(cond) instead of message(cond) in custom_condition()
custom_condition <- function(subclass, message, call = sys.call(-1), ...) {
cond <- condition(subclass, message, call = call, ...)
signalCondition(cond)
}
foo()
Run #: 1
Run #: 2
Run #: 3
So, this seems even more "unnecessary" that before as the function simply terminates.
This is how it would look with tryCatch():
tryCatch(
foo(),
conditionFooExample=function(cond) {
cond
},
conditionFooExample2=function(cond) {
cond
}
)
Run #: 1
<conditionFooExample in foo(): I'm just an example condition>
Well, the behavior itself does make sense, but I could have gotten the same result with an error condition.