By not using 'withCallingHandler' or 'tryCatch', the state is
like 'stopifnot' in R 3.4.x. If 'stopifnot' becomes faster than
in R 3.4.x when the expressions given to 'stopifnot' are all TRUE, it is
because 'match.call' is not called. Credit is to
https://github.com/HenrikBengtsson/Wishlist-for-R/issues/70 for the idea.
Speaking about 'match.call',
match.call()[[i+1L]]
can replace
match.call(expand.dots=FALSE)$...[[i]] .
Result of match.call() follows argument order in function definition. In
'stopifnot', '...' comes first.
Note that what I proposed lately was not merely
sys.call(sys.parent()) ,
but
if(p <- sys.parent()) sys.call(p) .
When sys.parent() is 0, which is the frame number of .GlobalEnv, the result is
NULL. The result is never the current call. I believe that it is the call of
sys.frame(sys.parent()) or parent.frame(), which is the frame where
stopifnot(...) is evaluated, like I said before.
sys.frame(0) is .GlobalEnv, but sys.call(0) is current call, the same as
sys.call() or sys.call(sys.nframe()). See
https://stat.ethz.ch/pipermail/r-devel/2016-March/072511.html .
As far as I can see, full stopifnot(...) call can only appear from an error that
happens during evaluation of an argument of 'stopifnot'. Because the
error is not raised by 'stopifnot', the call in the error has nothing to
do with how 'n' is computed in sys.call(n-1) , or even with use of
sys.call(n-1) itself.
if(n > 1) sys.call(n-1)
that I proposed previously was aimed to be like
sys.call(-1)
in 'stopifnot' in R 3.5.x. Negative number counts back from current
frame. The value of 'n' is sys.nframe() or (sys.nframe()-3). In my
patch, stopifnot(exprs=*) drives stopifnot(...) call via 'eval'. I found
that frames were generated for
stopifnot (exprs) -> eval -> eval (.Internal) -> stopifnot
(...)>From stopifnot (...) , reaching stopifnot (exprs) takes 3 steps back.
Showing full call in error is not unique to 'stopifnot'. In my E-mail in
https://stat.ethz.ch/pipermail/r-devel/2019-February/077386.html , I gave
identity(is.na(log()))
as an example. It gives
Error in identity(is.na(log())) :
? argument "x" is missing, with no default
Expanding further,
identity(identity(is.na(log())))
has the same error message, with only one call to 'identity'.
I guess that it is because 'log' and 'is.na' are primitive
functions, but 'identity' is not. I guess that a primitive function
doesn't have its own context, doesn't generate frame, so the innermost
non-primitive function is taken as context.
However,
identity(is.na(log("a")))
gives
Error in log("a") : non-numeric argument to mathematical function
I guess that some primitive functions in some cases modify call to be shown in
error or warning message.
options(error = expression(NULL))
library(compiler)
enableJIT(0)
f <- function(x) for (i in 1) x
f(is.numeric(y))
# Error: object 'y' not found
fc <- cmpfun(f)
fc(is.numeric(y))
# Error in fc(is.numeric(y)) : object 'y' not found
The above illustrates what happens in current 'stopifnot' without
'withCallingHandlers' or 'tryCatch'. For error during
'for', non-compiled and compiled versions are different. It surprised
me.
'stopifnot' without 'withCallingHandlers' and 'tryCatch'
is like in R 3.4.x. I had expected error from
stopifnot(is.numeric(y))
when 'y' doesn't exist to contain full 'stopifnot' call, as
in R 3.4.x. My idea of calling 'stopifnot' again for stopifnot(exprs=*)
was to avoid seeing 'eval' in error message of
stopifnot(exprs = { is.numeric(y) })
when 'y' doesn't exist, assuming that seeing
stopifnot(is.numeric(y))
in error message was OK. As an aside, calling 'eval' is faster than
calling 'eval' multiple times.
If it is really wanted that error from
stopifnot(is.numeric(y))
when 'y' doesn't exist doesn't give full stopifnot(...) call, I
think use of 'tryCatch' is unavoidable.
A minor advantage of 'assert' with 'do.call' is smaller
traceback() .
With my revised patch, the 'else' clause for 'cl' gives
call("expression", exprs) .
For
do.call(stopifnot, list(exprs = expression())) ,
the whole expression object is taken as one.
do.call(stopifnot, list(exprs = expression(1==1, 2 < 1, stop("NOT
GOOD!\n"))))
Error in do.call(stopifnot, list(exprs = expression(1 == 1, 2 < 1,
stop("NOT GOOD!\n")))) :
expression(1 == 1, 2 < 1, stop("NOT GOOD!\n")) are not all TRUE
To be the same as in R 3.5.x, the 'else' can be
as.call(c(quote(expression), as.expression(exprs)))
--------------------------------------------
On Wed, 6/3/19, Martin Maechler <maechler at stat.math.ethz.ch> wrote:
Subject: Re: [Rd] stopifnot
@r-project.org
Cc: "Martin Maechler" <maechler at stat.math.ethz.ch>
Date: Wednesday, 6 March, 2019, 3:50 PM
>>>>> Martin Maechler
>>>>>? ? on Tue, 5 Mar 2019 21:04:08 +0100 writes:
>>>>> Suharto Anggono Suharto Anggono
>>>>>? ? on Tue, 5 Mar 2019 17:29:20 +0000 writes:
? ? >> Another possible shortcut definition:
? ? >> assert <- function(exprs)
? ? >> do.call("stopifnot", list(exprs = substitute(exprs),
local = parent.frame()))
? ? > Thank you.? I think this is mostly a matter of taste, but I
? ? > liked your version using eval() & substitute() a bit more.? For
? ? > me, do.call() is a heavy hammer I only like to use when needed..
? ? > Or would there be advantages of this version?
? ? > Indeed (as you note below) one important consideration is the exact
? ? > message that is produced when one assertion fails.
? ? >> After thinking again, I propose to use
? ? >>? ? ? ?? stop(simpleError(msg, call = if(p <- sys.parent())
sys.call(p)))
? ? > That would of course be considerably simpler indeed,? part "2
a" of these:
? ? >> - It seems that the call is the call of the frame where
stopifnot(...) is evaluated. Because that is the correct context, I think it is
good.
? ? >> - It is simpler and also works for call that originally comes from
stopifnot(exprs=*) .
? ? >> - It allows shortcut ('assert') to have the same call in
error message as stopifnot(exprs=*) .
? ? > That may be another good reason in addition to code simplicity.
? ? > I will have to see if this extra simplification does not loose
? ? > more than I'd want.
? ? >> Another thing: Is it intended that
? ? >> do.call("stopifnot", list(exprs = expression()))
? ? >> evaluates each element of the expression object?
? ? > ??? I really don't know.? Even though such a case looks
? ? > "unusual" (to say the least), in principle I'd like that
? ? > expressions are evaluated sequentially until the first non-TRUE
? ? > result.? With a concrete example, I do like what we have
? ? > currently in unchanged R-devel, but also in R 3.5.x, i.e., in
? ? > the following, not any "NOT GOOD" should pop up:
? ? >> stopifnot(exprs = expression(1==1, 2 < 1, stop("NOT
GOOD!\n")))
? ? > Error: 2 < 1 is not TRUE
? ? >> do.call(stopifnot, list(exprs = expression(1==1, 2 < 1,
stop("NOT GOOD!\n"))))
? ? > Error in do.call(stopifnot, list(exprs = expression(1 == 1, 2 < 1,
cat("NOT GOOD!\n")))) :
? ? > 2 < 1 is not TRUE
? ? >>
? ? > Hmm, it seems I do not understand what you ask above in your
? ? > "Another thing: .."
? ? >> If so, maybe add a case for 'cl', like
? ? >>? ? ? ?? else if(is.expression(exprs))
? ? >>? ? ? ?? as.call(c(quote(expression), exprs))
? ? > that seems simple indeed, but at the moment, I cannot see one example
? ? > where it makes a difference ... or then I'm "blind" ..
???
? ? > Best,
? ? > Martin
Some more testing of examples lead me to keep the more
sophisticated "computation" of 'n'? for the? sys.call(n-1).
Main reason:? If one of the expression is not all TRUE, I really
don't want to see the full 'stopifnot(....)' call in the printed
error message.
I do want to encourage that? stopifnot()? asserts many things
and so its own call should really not be shown.
Also I really wanted to commit something, notably also fixing
the? stopifnot(exprs = T)? bug,? so R-devel (rev >= 76203 ) now
contains a simpler and much faster? stopifnot() than previously
[and than the R 3.5.x series].
I agree that the final decisions on getting a call (or not --
which was a very good idea by you!) and which parent's call
should be used? may deserve some future tinkering..
Thank you again, Suharto Anggono,
[[elided Yahoo spam]]
Martin