[Rd] stopifnot

Suharto Anggono Suharto Anggono @uh@rto_@nggono @end|ng |rom y@hoo@com
Sun Apr 14 21:56:33 CEST 2019

In current definition of function 'stopifnot' in stop.R in R 3.6.0 beta (https://svn.r-project.org/R/branches/R-3-6-branch/src/library/base/R/stop.R) or R devel (https://svn.r-project.org/R/trunk/src/library/base/R/stop.R), if 'exprs' is specified, cl[[1]] is quote(stopifnot) . To be more robust, quote(base::stopifnot) may be used instead.

Also, in current definition of function 'stopifnot' in R 3.6.0 beta or R devel, for 'cl' if 'exprs' is specified, there a case with comment "the *name* of an expression". The intent is allowing
stopifnot(exprs = ee) ,
where variable 'ee' holds an expression object, to work on the expression object.

It is not quite right to use eval(exprs) . It fails when 'stopifnot' is called inside a function, like
f <- function(ee) stopifnot(exprs = ee)

But, how about local=FALSE case? Should the following work?
f <- function(ee) stopifnot(exprs = ee, local = FALSE)

But, why bother making it work, while it is undocumented that 'exprs' argument in 'stopifnot' can be an expression? Well, yes, expectation may be set from the name "exprs" itself or from argument 'exprs' in function 'source' or 'withAutoprint'. Function 'withAutoprint' may be the closest match.

Function 'withAutoprint' has 'evaluated' argument that controls whether work is on value of  'exprs' or on 'exprs' as given. I like the approach.

If 'E1' is an expression object,
as.call(c(quote(stopifnot), E1))
also works, without converting 'E1' to list.

I suggest to arrange "details" section in stopifnot.Rd as follows:
This function is intended ...
Since R version 3.5.0, stopifnot(exprs = { ... }) ...
stopifnot(A, B) ... is conceptually equivalent to ...
Since R version 3.5.0, expressions are evaluated sequentially ...
Since R version 3.6.0, stopifnot no longer handles potential errors or warnings ...  ---not including sys.call(<n>)
Since R version 3.4.0, ... all.equal ...

Use of sys.call(<n>) in 'stopifnot' actually happens since R 3.5.0, as the call included in error message produced by 'stopifnot'. In R 3.5.x, it is sys.call(-1) , that can be NULL . In current R 3.6.0 beta, it is sys.call(sys.parent(1L)) , only if sys.parent(1L) is not 0. The two may differ only for 'stopifnot' that is called via 'eval' or the like.

I think it is good if the documentation also includes an example of use of 'stopifnot' inside a function, where error message from 'stopifnot' includes call since R 3.5.0. Such an example is in https://stat.ethz.ch/pipermail/r-devel/2017-May/074303.html .

On Mon, 1/4/19, Martin Maechler <maechler using stat.math.ethz.ch> wrote:

 Subject: Re: [Rd] stopifnot

 Cc: r-devel using r-project.org
 Date: Monday, 1 April, 2019, 8:12 PM

>>>>> Suharto Anggono Suharto Anggono via R-devel 
>>>>>    on Sun, 31 Mar 2019 15:26:13 +0000 writes:

[........ "eval() inside for()" not giving call in error message .....]

    > "Details" section of 'stopifnot' documentation in current R 3.6.0 alpha
    > (https://svn.r-project.org/R/branches/R-3-6-branch/src/library/base/man/stopifnot.Rd)
    > has this.

    >   Since \R version 3.6.0, \code{stopifnot()} no longer handles potential
    >   errors or warnings (by \code{\link{tryCatch}()} etc) for each single
    >   expression but rather aims at using the correct
    >   \code{\link{sys.call}(<n>)} to get the most meaningful error message in
    >   case of an error.  This provides considerably less overhead.

    > I think part of the first sentence starting from "but rather" should be removed because it is not true.

You are right that it is not accurate... I'll modify it,
including keeping the  "considerably less overhead"
which had been one important reason for changing from 3.5.x to
the current version.

    > The next paragraph:

    >   Since \R version 3.5.0, expressions \emph{are} evaluated sequentially,
    >   and hence evaluation stops as soon as there is a \dQuote{non-TRUE}, as
    >   indicated by the above conceptual equivalence statement.
    >   Further, when such an expression signals an error or
    >   \code{\link{warning}}, its \code{\link{conditionCall}()} no longer
    >   contains the full \code{stopifnot} call, but just the erroneous
    >   expression.

    > As I said earlier (https://stat.ethz.ch/pipermail/r-devel/2019-February/077386.html), the last sentence above is not entirely true. 

You are right to some degree:  That really was true for R 3.5.x,
but is no longer entirely accurate.

It is still true currently interestingly thanks to the "eval() in for()"
behavior that the error/warning message is most of the time only
about the relevant part and not mentioning the full stopifnot(..) call.

    > It may say something like:
    > Further, when such an expression signals an error, stopifnot() in R 3.5.x makes its conditionCall() the erroneous expression, but no longer since R 3.6.0.

    > Is it OK that, for
    > do.call(stopifnot, list(exprs = expression())) ,
    > the whole expression object is taken as one?

You are right; that's not so nice.
On one hand, this is fine, as there is nothing not TRUE :

  > stopifnot()          
  > stopifnot(exprs = {})

but here,

  > do.call(stopifnot, list(exprs = expression())) 
  Error in do.call(stopifnot, list(exprs = expression())) : 
    expression() are not all TRUE

I'm about to commit a version [mostly from your suggestions],
where the above do.call() works as well.

    > End portion from running
    > example(stopifnot)
    > in R 3.5.0:

    stpfnt> stopifnot(all.equal(pi, 3.141593),  2 < 2, all(1:10 < 12), "a" < "b")
    > Error in eval(ei, envir) : pi and 3.141593 are not equal:
    >   Mean relative difference: 1.102658e-07

    > To me, "in eval(*)" is rather surprising and annoying and doesn't add clarity. Yes, stop() gives the same. But, in this case, just "Error", like in R before version 3.5.0, feels better to me. If
    > stop(simpleError(msg, call = if(p <- sys.parent()) sys.call(p)))
    > were used in 'stopifnot', just "Error" would be given in this case.

And you are right again... in my current version I do use your

  stop(simpleError(msg, call = if(p <- sys.parent()) sys.call(p)))

and that does solve the above.


More information about the R-devel mailing list