A trap for young players with the lapply() function.

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
9 messages Options
Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

A trap for young players with the lapply() function.

Rolf Turner

 From time to time I get myself into a state of bewilderment when using
apply() by calling it with FUN equal to a function which has an
"optional" argument named "X".

E.g.

     xxx <- lapply(y,function(x,X){cos(x*X)},X=2*pi)

which produces the error message

> Error in get(as.character(FUN), mode = "function", envir = envir) :
>   object 'y' of mode 'function' was not found

This of course happens because the name of the first argument of
lapply() is "X" and so it takes the value of this first argument to be
the supplied X (2*pi in the foregoing example) and then expects what the
user has denoted by "y" to be the value of FUN, and (obviously!) it isn't.

Once one realises what is going on, it's all quite obvious, and usually
pretty easy to fix.  OTOH there are lots of functions around with second
or third arguments whose formal name is "X", and these can trip one up
until the penny drops.

This keeps happening to me, over and over again (with sufficiently long
intervals between occurrences so that my ageing memory forgets the
previous occurrence).

Is there any way to trap/detect the use of an optional argument called
"X" and thereby issue a more perspicuous error message?

This would be helpful to those users who, like myself, are bears of very
little brain.

Failing that (it does look impossible) might it not be a good idea to
add a warning to the help for lapply(), to the effect that if FUN has an
optional argument named "X" then passing this argument via "..." will
cause this argument to be taken as the first argument to lapply() and
thereby induce an error?

cheers,

Rolf Turner

--
Technical Editor ANZJS
Department of Statistics
University of Auckland
Phone: +64-9-373-7599 ext. 88276

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: A trap for young players with the lapply() function.

Barry Rowlingson
On Mon, Mar 27, 2017 at 1:17 AM, Rolf Turner <[hidden email]> wrote:
>
> Is there any way to trap/detect the use of an optional argument called
> "X" and thereby issue a more perspicuous error message?
>
> This would be helpful to those users who, like myself, are bears of very
> little brain.
>
> Failing that (it does look impossible)

You can get the names of named arguments:

 > z = function(x,X){cos(x*X)}
 > names(formals(z))
 [1] "x" "X"


> might it not be a good idea to
> add a warning to the help for lapply(), to the effect that if FUN has an
> optional argument named "X" then passing this argument via "..." will
> cause this argument to be taken as the first argument to lapply() and
> thereby induce an error?

Another idea might be to use purrr:map instead, which is quite happy
with X in your function:

 >  xxx <- purrr::map(y,function(x,X){cos(x*X)},X=2*pi)
 > xxx
[[1]]
[1] 0.08419541

[[2]]
[1] 0.6346404

[[3]]
[1] 0.9800506

[[4]]
[1] 0.8686734

[[5]]
[1] -0.9220073

But don't feed `.x` to your purrrring cats, or fails silently:

 >  xxx <- purrr::map(y,function(x,.x){cos(x*.x)},.x=2*pi)
 > xxx
[[1]]
NULL

But who would have a function with `.x` as an argument?


> ______________________________________________
> [hidden email] mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: A trap for young players with the lapply() function.

Rolf Turner
On 28/03/17 04:21, Barry Rowlingson wrote:

> On Mon, Mar 27, 2017 at 1:17 AM, Rolf Turner <[hidden email]> wrote:
>>
>> Is there any way to trap/detect the use of an optional argument called
>> "X" and thereby issue a more perspicuous error message?
>>
>> This would be helpful to those users who, like myself, are bears of very
>> little brain.
>>
>> Failing that (it does look impossible)
>
> You can get the names of named arguments:
>
>  > z = function(x,X){cos(x*X)}
>  > names(formals(z))
>  [1] "x" "X"

That doesn't seem to help.  I tried putting a browser inside lapply()
and looked at formals(FUN).  This gave NULL, because FUN has already
been taken to be the list argument "x" to which lapply() is being applied.

>> might it not be a good idea to
>> add a warning to the help for lapply(), to the effect that if FUN has an
>> optional argument named "X" then passing this argument via "..." will
>> cause this argument to be taken as the first argument to lapply() and
>> thereby induce an error?
>
> Another idea might be to use purrr:map instead, which is quite happy
> with X in your function:
>
>  >  xxx <- purrr::map(y,function(x,X){cos(x*X)},X=2*pi)
>  > xxx
> [[1]]
> [1] 0.08419541
>
> [[2]]
> [1] 0.6346404
>
> [[3]]
> [1] 0.9800506
>
> [[4]]
> [1] 0.8686734
>
> [[5]]
> [1] -0.9220073
>
> But don't feed `.x` to your purrrring cats, or fails silently:
>
>  >  xxx <- purrr::map(y,function(x,.x){cos(x*.x)},.x=2*pi)
>  > xxx
> [[1]]
> NULL
>
> But who would have a function with `.x` as an argument?

Indeed.  It struck me that a possible workaround would be to change the
name of the first argument of lapply() from "X" to ".X".  No-one would
have a function with an argument names ".X" --- at least I wouldn't, so
this would solve the problem for me.

It seems to me that this change could be made without breaking anything.
But perhaps I am engaging in my usual PollyAnna-ish optimism! :-)

cheers,

Rolf

--
Technical Editor ANZJS
Department of Statistics
University of Auckland
Phone: +64-9-373-7599 ext. 88276

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: A trap for young players with the lapply() function.

Charles C. Berry
In reply to this post by Rolf Turner
On Mon, 27 Mar 2017, Rolf Turner wrote:

>
> From time to time I get myself into a state of bewilderment when using
> apply() by calling it with FUN equal to a function which has an "optional"
> argument named "X".
>
> E.g.
>
>    xxx <- lapply(y,function(x,X){cos(x*X)},X=2*pi)
>
> which produces the error message
>
>> Error in get(as.character(FUN), mode = "function", envir = envir) :
>>   object 'y' of mode 'function' was not found
>
> This of course happens because the name of the first argument of lapply() is
> "X" and so it takes the value of this first argument to be the supplied X
> (2*pi in the foregoing example) and then expects what the user has denoted by
> "y" to be the value of FUN, and (obviously!) it isn't.
>

The lapply help page addresses this issue in `Details' :

"it is good practice to name the first two arguments X and FUN if ... is
passed through: this both avoids partial matching to FUN and ensures that
a sensible error message is given if arguments named X or FUN are passed
through ..."

So that advice suggests something like:

xxx <- lapply( X=y, FUN=function(X,x){cos(X*x)}, x=2*pi )

Best,

Chuck

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: A trap for young players with the lapply() function.

Rolf Turner
On 28/03/17 15:26, Charles C. Berry wrote:

> On Mon, 27 Mar 2017, Rolf Turner wrote:
>
>>
>> From time to time I get myself into a state of bewilderment when using
>> apply() by calling it with FUN equal to a function which has an
>> "optional" argument named "X".
>>
>> E.g.
>>
>>    xxx <- lapply(y,function(x,X){cos(x*X)},X=2*pi)
>>
>> which produces the error message
>>
>>> Error in get(as.character(FUN), mode = "function", envir = envir) :
>>>   object 'y' of mode 'function' was not found
>>
>> This of course happens because the name of the first argument of
>> lapply() is "X" and so it takes the value of this first argument to be
>> the supplied X (2*pi in the foregoing example) and then expects what
>> the user has denoted by "y" to be the value of FUN, and (obviously!)
>> it isn't.
>>
>
> The lapply help page addresses this issue in `Details' :
>
> "it is good practice to name the first two arguments X and FUN if ... is
> passed through: this both avoids partial matching to FUN and ensures
> that a sensible error message is given if arguments named X or FUN are
> passed through ..."
>
> So that advice suggests something like:
>
> xxx <- lapply( X=y, FUN=function(X,x){cos(X*x)}, x=2*pi )


That is of course very sound advice, but it pre-supposes that the user
is *aware* that there is a pitfall to be avoided.  I was hoping for
something that would protect dweebs like myself from the pitfall given
that we are too obtuse to be cognizant of its existence.

I think that the suggestion I made, in response to a posting by Barry
Rowlingson, that the first argument of lapply() be given the name of
".X" rather than just-plain-X, would be (a) effective, and (b) harmless.

cheers,

Rolf

--
Technical Editor ANZJS
Department of Statistics
University of Auckland
Phone: +64-9-373-7599 ext. 88276

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: A trap for young players with the lapply() function.

R devel mailing list
>I think that the suggestion I made, in response to a posting by Barry >Rowlingson, that the first argument of lapply() be given the name of ".X" rather >than just-plain-X, would be (a) effective, and (b) harmless.

It would break any call to *apply() that used X= to name the first
argument.  There are currently 3020 such calls in the R code in CRAN.

One can avoid the problem by creating the function given as the FUN
argument when one calls lapply() and the like.  Don't give that
function arguments named "X", "FUN", "USE.NAMES", etc. and perhaps
make use of R's lexical scoping to avoid having to use many arguments
to the function.  E.g., instead of
    sapply(1:5, sin)
use
    sapply(1:5, function(theta) sin(theta))
or instead of
    myY <- 3
    sapply(1:5, atan2, y=myY)
use
    myY <- 3
    sapply(1:5, function(x) atan2(myY, x))

Bill Dunlap
TIBCO Software
wdunlap tibco.com


On Tue, Mar 28, 2017 at 2:30 PM, Rolf Turner <[hidden email]> wrote:

> On 28/03/17 15:26, Charles C. Berry wrote:
>>
>> On Mon, 27 Mar 2017, Rolf Turner wrote:
>>
>>>
>>> From time to time I get myself into a state of bewilderment when using
>>> apply() by calling it with FUN equal to a function which has an
>>> "optional" argument named "X".
>>>
>>> E.g.
>>>
>>>    xxx <- lapply(y,function(x,X){cos(x*X)},X=2*pi)
>>>
>>> which produces the error message
>>>
>>>> Error in get(as.character(FUN), mode = "function", envir = envir) :
>>>>   object 'y' of mode 'function' was not found
>>>
>>>
>>> This of course happens because the name of the first argument of
>>> lapply() is "X" and so it takes the value of this first argument to be
>>> the supplied X (2*pi in the foregoing example) and then expects what
>>> the user has denoted by "y" to be the value of FUN, and (obviously!)
>>> it isn't.
>>>
>>
>> The lapply help page addresses this issue in `Details' :
>>
>> "it is good practice to name the first two arguments X and FUN if ... is
>> passed through: this both avoids partial matching to FUN and ensures
>> that a sensible error message is given if arguments named X or FUN are
>> passed through ..."
>>
>> So that advice suggests something like:
>>
>> xxx <- lapply( X=y, FUN=function(X,x){cos(X*x)}, x=2*pi )
>
>
>
> That is of course very sound advice, but it pre-supposes that the user is
> *aware* that there is a pitfall to be avoided.  I was hoping for something
> that would protect dweebs like myself from the pitfall given that we are too
> obtuse to be cognizant of its existence.
>
> I think that the suggestion I made, in response to a posting by Barry
> Rowlingson, that the first argument of lapply() be given the name of ".X"
> rather than just-plain-X, would be (a) effective, and (b) harmless.
>
> cheers,
>
> Rolf
>
> --
> Technical Editor ANZJS
> Department of Statistics
> University of Auckland
> Phone: +64-9-373-7599 ext. 88276
>
> ______________________________________________
> [hidden email] mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: A trap for young players with the lapply() function.

Rolf Turner

On 29/03/17 11:03, William Dunlap wrote:

>> I think that the suggestion I made, in response to a posting by
>> Barry Rowlingson, that the first argument of lapply() be given the name of
>> ".X" rather than just-plain-X, would be (a) effective, and (b) harmless.
>
> It would break any call to *apply() that used X= to name the first
> argument. There are currently 3020 such calls in the R code in CRAN.

Okay.  Scratch that idea.

> One can avoid the problem by creating the function given as the FUN
> argument when one calls lapply() and the like.  Don't give that
> function arguments named "X", "FUN", "USE.NAMES", etc. and perhaps
> make use of R's lexical scoping to avoid having to use many arguments
> to the function.  E.g., instead of
>     sapply(1:5, sin)
> use
>     sapply(1:5, function(theta) sin(theta))
> or instead of
>     myY <- 3
>     sapply(1:5, atan2, y=myY)
> use
>     myY <- 3
>     sapply(1:5, function(x) atan2(myY, x))

Again, all very sound advice but it does not address the problem of
there being a trap for young players.  The advice can only be applied by
a user only if the user is *aware* of the trap.

At this point it would appear that the problem is fundamentally
unsolvable. :-(

cheers,

Rolf

--
Technical Editor ANZJS
Department of Statistics
University of Auckland
Phone: +64-9-373-7599 ext. 88276

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: A trap for young players with the lapply() function.

Enrico Schumann-2
In reply to this post by Rolf Turner
(inline)

On Tue, 28 Mar 2017, Rolf Turner writes:

> On 28/03/17 04:21, Barry Rowlingson wrote:
>> On Mon, Mar 27, 2017 at 1:17 AM, Rolf Turner <[hidden email]> wrote:
>>>
>>> Is there any way to trap/detect the use of an optional argument called
>>> "X" and thereby issue a more perspicuous error message?
>>>
>>> This would be helpful to those users who, like myself, are bears of very
>>> little brain.
>>>
>>> Failing that (it does look impossible)
>>
>> You can get the names of named arguments:
>>
>>  > z = function(x,X){cos(x*X)}
>>  > names(formals(z))
>>  [1] "x" "X"
>
> That doesn't seem to help.  I tried putting a browser inside lapply()
> and looked at formals(FUN).  This gave NULL, because FUN has already
> been taken to be the list argument "x" to which lapply() is being
> applied.

You can get the call, without the arguments being
matched, with `sys.call`. In your call of lapply,
saying `sys.call()` before anything else would give
you


    lapply(y, function(x, X) {
        cos(x * X)
    }, X = 2 * pi)

which would allow you to get at the argument names of
your function.

    if ("X" %in% names(sys.call()[[3L]][[2L]]))
       warnings("found 'X'")

But more would be needed: you need to figure out which
argument you actually meant to be FUN. (In the above,
I simply assumed it was the second passed argument.)
That is, you would need to figure out which passed
argument is a function, which is not safe either,
since ... may also contain functions.


>>> might it not be a good idea to
>>> add a warning to the help for lapply(), to the effect that if FUN has an
>>> optional argument named "X" then passing this argument via "..." will
>>> cause this argument to be taken as the first argument to lapply() and
>>> thereby induce an error?
>>
>> Another idea might be to use purrr:map instead, which is quite happy
>> with X in your function:
>>
>>  >  xxx <- purrr::map(y,function(x,X){cos(x*X)},X=2*pi)
>>  > xxx
>> [[1]]
>> [1] 0.08419541
>>
>> [[2]]
>> [1] 0.6346404
>>
>> [[3]]
>> [1] 0.9800506
>>
>> [[4]]
>> [1] 0.8686734
>>
>> [[5]]
>> [1] -0.9220073
>>
>> But don't feed `.x` to your purrrring cats, or fails silently:
>>
>>  >  xxx <- purrr::map(y,function(x,.x){cos(x*.x)},.x=2*pi)
>>  > xxx
>> [[1]]
>> NULL
>>
>> But who would have a function with `.x` as an argument?
>
> Indeed.  It struck me that a possible workaround would be to change
> the name of the first argument of lapply() from "X" to ".X".  No-one
> would have a function with an argument names ".X" --- at least I
> wouldn't, so this would solve the problem for me.
>
> It seems to me that this change could be made without breaking anything.
> But perhaps I am engaging in my usual PollyAnna-ish optimism! :-)
>
> cheers,
>
> Rolf

--
Enrico Schumann
Lucerne, Switzerland
http://enricoschumann.net

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: A trap for young players with the lapply() function.

Rolf Turner
On 29/03/17 20:32, Enrico Schumann wrote:

> (inline)
>
> On Tue, 28 Mar 2017, Rolf Turner writes:
>
>> On 28/03/17 04:21, Barry Rowlingson wrote:
>>> On Mon, Mar 27, 2017 at 1:17 AM, Rolf Turner <[hidden email]> wrote:
>>>>
>>>> Is there any way to trap/detect the use of an optional argument called
>>>> "X" and thereby issue a more perspicuous error message?
>>>>
>>>> This would be helpful to those users who, like myself, are bears of very
>>>> little brain.
>>>>
>>>> Failing that (it does look impossible)
>>>
>>> You can get the names of named arguments:
>>>
>>>  > z = function(x,X){cos(x*X)}
>>>  > names(formals(z))
>>>  [1] "x" "X"
>>
>> That doesn't seem to help.  I tried putting a browser inside lapply()
>> and looked at formals(FUN).  This gave NULL, because FUN has already
>> been taken to be the list argument "x" to which lapply() is being
>> applied.
>
> You can get the call, without the arguments being
> matched, with `sys.call`. In your call of lapply,
> saying `sys.call()` before anything else would give
> you
>
>
>     lapply(y, function(x, X) {
>         cos(x * X)
>     }, X = 2 * pi)
>
> which would allow you to get at the argument names of
> your function.
>
>     if ("X" %in% names(sys.call()[[3L]][[2L]]))
>        warnings("found 'X'")
>
> But more would be needed: you need to figure out which
> argument you actually meant to be FUN. (In the above,
> I simply assumed it was the second passed argument.)
> That is, you would need to figure out which passed
> argument is a function, which is not safe either,
> since ... may also contain functions.


This idea appears to work for the particular example that I used, but it
is not clear to me how to make it work in general.  E.g. if we define

   foo <- function(x,X){X*x}

and then do

   lapply(xxx,foo,X=2*pI)

we find that sys.call[[3]] is of length 1 and consists only of the
*name* "foo".  One can then inspect

    formals(get(as.character(sys.call[[3]])))

and find "X" therein, on the basis of which to trigger a warning.

However I don't know how to approach distinguish the two cases, or how
to discern if there may be other cases lurking in the bushes.

So the problem still looks insoluble to me.

cheers,

Rolf

--
Technical Editor ANZJS
Department of Statistics
University of Auckland
Phone: +64-9-373-7599 ext. 88276

______________________________________________
[hidden email] mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
Loading...