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
,warning
orerror
)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.