[R] A stopifnot() nastiness, even if not a bug
Martin Maechler
m@ech|er @end|ng |rom @t@t@m@th@ethz@ch
Sat May 9 17:33:01 CEST 2020
>>>>> Martin Maechler
>>>>> on Mon, 13 Apr 2020 22:30:35 +0200 writes:
>>>>> William Dunlap
>>>>> on Mon, 13 Apr 2020 09:57:11 -0700 writes:
>> You can avoid the problem in Martin's example by only giving scalars to
>> stopifnot(). E.g., using stopifnot(all(x>0)) or stopifnot(length(x)==1,
x> 0) instead of stopifnot(x>0). I think having stopifnot call
>> all(predicate) if length(predicate)!=1 was probably a mistake.
> well, maybe.
> As I brought up the 0-length example: One could think of making
> an exception for logical(0) and treat that as non-TRUE.
> (for R-devel only, [......])
> Martin
I have been a bit sad that nobody (not even Hervé) reacted to my
proposal, 4 weeks ago.
As I agree that it is safer for stopifnot() to be less lenient
here, and not allow the usual behavior of logical(0) to be
treated as TRUE, namely as in all(logical(0)) |--> TRUE ,
I had actually implemented the above proposal in my own version of R-devel,
(but not committed!), nicely introducing a new optional argument
'allow.logical0' where
- allow.logical0 = FALSE is the new default
- allow.logical0 = TRUE is back compatible
What I found is that this (not back compatible) change lead to a
few test breakages also in R & recommended packages, and IIRC in
a few of my own packages. Still probably only in about 1 in 1000
of the stopifnot cases, but in practically all cases, the breakage was
"wrong" in the sense that {conceptual example}
stopifnot( f1(x) == f2(x) )
should test (almost, say apart from names(.)) identical behavior
of f1() and f2() and that would naturally also extend to the
case of 0-extent 'x'.
So I had to change the above (half a dozen, say) cases to
stopifnot( f1(x) == f2(x) , allow.logical0 = TRUE)
to keep the test working as it was intended to.
The nice thing about the change is that it is also working in
current versions of R where allow.logical is not a special
argument and just treated as part of '...' and is it TRUE, does
not change the semantic of stopifnot() in current (possibly,
then, "previous") versions of R.
Overall I think it may be a good idea to consider this
not-back-compatible change to stopifnot()
(if only to get Hervé into continue using it ! ;-) ;-))
BUT I assume quite a few other people may have to get used to
see the following error in their stopifnot() code and will have
to add occasional 'allow.logical0 = TRUE' to those cases the
old behavior was really the intended one.
(I will have to finally get Matrix 1.3-0 released to CRAN
before committing the change to R-devel, and I may also ask
help of someone to check all CRAN/Bioc against that change so I
can alert package authors who need to adapt).
Please let us know your thoughts on this.
Martin
>> On Mon, Apr 13, 2020 at 9:28 AM Hervé Pagès <hpages using fredhutch.org> wrote:
>>>
>>>
>>> On 4/13/20 05:30, Martin Maechler wrote:
>>> >>>>>> peter dalgaard
>>> >>>>>> on Mon, 13 Apr 2020 12:00:38 +0200 writes:
>>> >
>>> > > Inline...
>>> > >> On 13 Apr 2020, at 11:15 , Martin Maechler <
>>> maechler using stat.math.ethz.ch> wrote:
>>> > >>
>>> > >>>>>>> Bert Gunter
>>> > >>>>>>> on Sun, 12 Apr 2020 16:30:09 -0700 writes:
>>> > >>
>>> > >>> Don't know if this has come up before, but ...
>>> > >>>> x <- c(0,0)
>>> > >>>> length(x)
>>> > >>> [1] 2
>>> > >>> ## but
>>> > >>>> stopifnot(length(x))
>>> > >>> Error: length(x) is not TRUE
>>> > >>> Called from: top level
>>> > >>> ## but
>>> > >>>> stopifnot(length(x) > 0) ## not an error; nor is
>>> > >>>> stopifnot(as.logical(length(x)))
>>> > >>> ## Ouch!
>>> > >>
>>> > >>> Maybe the man page should say something about not assuming
>>> automatic
>>> > >>> coercion to logical, which is the usual expectation. Or fix
>>> this.
>>> > >>
>>> > >>> Bert Gunter
>>> > >>
>>> > >> Well, what about the top most paragraph of the help page is not
>>> clear here ?
>>> > >>
>>> > >>> Description:
>>> > >>
>>> > >>> If any of the expressions (in '...' or 'exprs') are not 'all'
>>> > >>> 'TRUE', 'stop' is called, producing an error message indicating
>>> > >>> the _first_ expression which was not ('all') true.
>>> > >>
>>> >
>>> > > This, however, is somewhat less clear:
>>> >
>>> > > ..., exprs: any number of (typically but not necessarily
>>> ‘logical’) R
>>> > > expressions, which should each evaluate to (a logical vector
>>> > > of all) ‘TRUE’. Use _either_ ‘...’ _or_ ‘exprs’, the latter
>>> >
>>> > > What does it mean, "typically but not necessarily ‘logical’"?
>>> >
>>> > That's a good question: The '(....)' must have been put there a while
>>> ago.
>>> > I agree that it's not at all helpful. Strictly, we are really
>>> > dealing with unevaluated expressions anyway ("promises"), but
>>> > definitely all of them must evaluate to logical (vector or
>>> > array..) of all TRUE values. In the very beginning of
>>> > stopifnot(), I had thought that it should also work in other
>>> > cases, e.g., for Matrix(TRUE, 4,5) {from the Matrix package} etc,
>>> > but several use cases had convinced us / me that stopifnot
>>> > should be stricter...
>>> >
>>> > > The code actually tests explicitly with is.logical, as far as I
>>> can tell.
>>> >
>>> > > This creates a discrepancy between if(!...)stop(...) and
>>> stopifnot(),
>>> >
>>> > yes indeed, on purpose now, for a very long time ...
>>> >
>>> > There's another discrepancy, more dangerous I think,
>>> > as shown in the following
>>> > {Note this discrepancy has been noted for a long time .. also on
>>> > this R-devel list} :
>>> >
>>> > m <- matrix(1:12, 3,4)
>>> > i <- (1:4) %% 2 == 1 & (0:3) %% 5 == 0
>>> >
>>> > stopifnot(dim(m[,i]) == c(3,1)) # seems fine
>>> >
>>> > if(dim(m[,i]) != c(3,1)) stop("wrong dim") # gives an error (but not
>>> ..)
>>>
>>> mmh... that is not good. I was under the impression that we could at
>>> least expect 'stopifnot(x)' to be equivalent to 'if (!isTRUE(x))
>>> stop(...)'. I'll have to revisit my use of stopifnot() in many many
>>> places... again :-/ Or may be just stop using it and use 'if
>>> (!isTRUE(...))' instead.
>>>
>>> H.
>>>
>>> >
>>> >
>>> > Martin
>>> >
>>> > >> as in
>>> > >> f <- function (x) if (!x) stop(paste(deparse(substitute(x)), "is
>>> not TRUE"))
>>> > >> f(0)
>>> > > Error in f(0) : 0 is not TRUE
>>> > >> f(1)
>>> > >> stopifnot(0)
>>> > > Error: 0 is not TRUE
>>> > >> stopifnot(1)
>>> > > Error: 1 is not TRUE
>>> >
>>> > > -pd
>>> >
>>> >
>>> > >> If useR's expectations alone would guide the behavior of a
>>> > >> computer language, the language would have to behave
>>> > >> "personalized" and give different results depending on the user,
>>> > >> which may be desirable in medicine or psychotherapy but not with
>>> R.
>>> > >> Martin
More information about the R-help
mailing list