[Rd] stopifnot -- eval(*) inside for()

Suharto Anggono Suharto Anggono @uh@rto_@nggono @end|ng |rom y@hoo@com
Wed Apr 3 04:40:24 CEST 2019


With
f <- function(x) for (i in 1) x
fc <- cmpfun(f)
(my previous example), error message of
fc(is.numeric(y))
shows the originating call as well, while error message of
f(is.numeric(y))
doesn't. Compiled version behaves differently.

Even with
f <- function(x) for (i in 1) {x; eval(expression(i))}
fc <- cmpfun(f)
, error message of
fc(is.numeric(y))
shows the originating call in R 3.3.1.


As I see, error message only has one line of call. If the deparsed call spans more than one line, the rest is not shown.


In 'stopifnot' in R 3.5.x, each is wrapped in 'tryCatch' which is wrapped again in 'withCallingHandlers'. Just one wrapping may be enough. The 'withCallingHandlers' construct in 'stopifnot' in R 3.5.x has no effect anyway, as I said before (https://stat.ethz.ch/pipermail/r-devel/2019-February/077386.html). Also, 'tryCatch' (or 'withCallingHandlers' ...) can wrap the entire 'for' loop. The slowdown can be less than in R 3.5.x.

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

 Subject: Re: [Rd] stopifnot -- eval(*) inside for()

 Cc: r-devel using r-project.org
 Date: Monday, 1 April, 2019, 5:00 PM
 
>>>>> Suharto Anggono Suharto Anggono via R-devel 
>>>>>    on Sun, 31 Mar 2019 15:26:13 +0000 writes:

    > Ah, with R 3.5.0 or R 3.4.2, but not with R 3.3.1, 'eval'
    > inside 'for' makes compiled version behave like
    > non-compiled version. 

Ah.. ... thank you for detecting that  " eval() inside for()" behaves
specially  in how error message get a call or not.
Let's focus only on this issue here.

I'm adding a 0-th case to make even clearer what you are saying:

  >  options(error = expression(NULL))
  >  library(compiler)
  >  enableJIT(0)

  > f0 <- function(x) { x ; x^2 } ; f0(is.numeric(y))
  Error in f0(is.numeric(y)) (from #1) : object 'y' not found
  > (function(x) { x ; x^2 })(is.numeric(y))
  Error in (function(x) { (from #1) : object 'y' not found
  > f0c <- cmpfun(f0) ; f0c(is.numeric(y))

so by default, not only the error message but the originating
call is shown as well.

However, here's your revealing examples:

  > f <- function(x) for (i in 1) {x; eval(expression(i))}
  > f(is.numeric(y))
  > # Error: object 'y' not found
  > fc <- cmpfun(f)
  > fc(is.numeric(y))
  > # Error: object 'y' not found

I've tried more examples and did not find any difference
between simple interpreted and bytecompiled code {apart
from "keep.source=TRUE" keeping source, sometimes visible}.
So I don't understand yet why you think the byte compiler plays
a role.

Rather the crucial difference seems  the error happens inside a
loop which contains an explicit eval(.), and that eval() may
even be entirely unrelated to the statement in which the error
happens [above: The error happens when the promise 'x' is
evaluated, *before* eval() is called at all].


    > Is this accidental feature going to be relied upon?

    [i.e.  *in  stopifnot() R code (which in R-devel and R 3.5.x has
            had an eval() inside the for()-loop)]

That is a good question.
What I really like about the R-devel case:  We do get errors
signalled that do *not* contain the full stopifnot() call.

With the newish introduction of the `exprs = { ... ... }` variant,
it is even more natural to have large `exprs` in a stopifnot() call,
and when there's one accidental error in there, it's quite
unhelpful to see the full stopifnot(..........) call {many lines
of R code} obfuscating the one statement which produced the
error.

So it seems I am asking for a new feature in R, 
namely to temporarily say: Set the call to errors to NULL "in

the following".

In R 3.5.x, I had used withCallingHandlers(...) to achieve that
and do even similar for warnings... but needed to that for every
expression and hence inside the for loop  and the consequence
was a relatively large slowdown of stopifnot()..  which
triggered all the changes since.

Whereas what we see here ["eval() inside for()"] is a cheap
automatic suppression of 'call' for the "internal errors", i.e.,
those we don't trigger ourselves via stop(simplError(...)).



More information about the R-devel mailing list