[Rd] codetools wrongly complains about lazy evaluation in S4 methods

Hervé Pagès hp@ge@@on@g|thub @end|ng |rom gm@||@com
Thu Jun 15 23:25:17 CEST 2023


Oh but I see now that you've already tried this in your R/AllGenerics.R, 
sorry for missing that, but that you worry about the following message 
being disruptive on CRAN:

     The following object is masked from 'package:base':

         qr.X

Why would that be? As long as you only define methods for objects that 
**you** control everything is fine. In other words you're not allowed to 
define a method for "qr" objects because that method would override 
base::qr.X(). But the generic itself and the method that you define for 
your objects don't override anything so should not disrupt anything.

H.

On 6/15/23 13:51, Hervé Pagès wrote:
>
> I'd argue that at the root of the problem is that your qr.X() generic 
> dispatches on all its arguments, including the 'ncol' argument which I 
> think the dispatch mechanism needs to evaluate **before** dispatch can 
> actually happen.
>
> So yes lazy evaluation is a real feature but it does not play well for 
> arguments of a generic that are involved in the dispatch.
>
> If you explicitly defined your generic with:
>
>    setGeneric("qr.X", signature="qr")
>
> you should be fine.
>
> More generally speaking, it's a good idea to restrict the signature of 
> a generic to the arguments "that make sense". For unary operations 
> this is usually the 1st argument, for binary operations the first two 
> arguments etc... Additional arguments that control the operation like 
> modiflers, toggles, flags, rng seed, and other parameters, usually 
> have not place in the signature of the generic.
>
> H.
>
> On 6/14/23 20:57, Mikael Jagan wrote:
>> Thanks all - yes, I think that Simon's diagnosis ("user error") is 
>> correct:
>> in this situation one should define a reasonable generic function 
>> explicitly,
>> with a call to setGeneric, and not rely on the call inside of 
>> setMethod ...
>>
>> But it is still not clear what the way forward should be (for package 
>> Matrix,
>> where we would like to export a method for 'qr.X').  If we do 
>> nothing, then
>> there is the note, already mentioned:
>>
>>     * checking R code for possible problems ... NOTE
>>     qr.X: no visible binding for global variable ‘R’
>>     Undefined global functions or variables:
>>       R
>>
>> If we add the following to our R/AllGenerics.R :
>>
>>     setGeneric("qr.X",
>>                function(qr, complete = FALSE, ncol, ...)
>>                    standardGeneric("qr.X"),
>>                useAsDefault = function(qr, complete = FALSE, ncol, 
>> ...) {
>>                    if(missing(ncol))
>>                        base::qr.X(qr, complete = complete)
>>                    else base::qr.X(qr, complete = complete, ncol = ncol)
>>                },
>>                signature = "qr")
>>
>> then we get a startup message, which would be quite disruptive on CRAN :
>>
>>     The following object is masked from 'package:base':
>>
>>         qr.X
>>
>> and if we further add setGenericImplicit("qr.X", restore = (TRUE|FALSE))
>> to our R/zzz.R, then for either value of 'restore' we encounter :
>>
>>     ** testing if installed package can be loaded from temporary 
>> location
>>     Error: package or namespace load failed for 'Matrix':
>>      Function found when exporting methods from the namespace 
>> 'Matrix' which is not S4 generic: 'qr.X'
>>
>> Are there possibilities that I have missed?
>>
>> It seems to me that the best option might be to define an implicit 
>> generic
>> 'qr.X' in methods via '.initImplicitGenerics' in 
>> methods/R/makeBasicFunsList.R,
>> where I see that an implicit generic 'qr.R' is already defined ... ?
>>
>> The patch pasted below "solves everything", though we'd still have to 
>> think
>> about how to work for versions of R without the patch ...
>>
>> Mikael
>>
>> Index: src/library/methods/R/makeBasicFunsList.R
>> ===================================================================
>> --- src/library/methods/R/makeBasicFunsList.R    (revision 84541)
>> +++ src/library/methods/R/makeBasicFunsList.R    (working copy)
>> @@ -263,6 +263,17 @@
>>             signature = "qr", where = where)
>>      setGenericImplicit("qr.R", where, FALSE)
>>
>> +    setGeneric("qr.X",
>> +               function(qr, complete = FALSE, ncol, ...)
>> +                   standardGeneric("qr.X"),
>> +               useAsDefault = function(qr, complete = FALSE, ncol, 
>> ...) {
>> +                   if(missing(ncol))
>> +                       base::qr.X(qr, complete = complete)
>> +                   else base::qr.X(qr, complete = complete, ncol = 
>> ncol)
>> +               },
>> +               signature = "qr", where = where)
>> +    setGenericImplicit("qr.X", where, FALSE)
>> +
>>      ## our toeplitz() only has 'x'; want the generic "here" rather 
>> than "out there"
>>      setGeneric("toeplitz", function(x, ...) 
>> standardGeneric("toeplitz"),
>>             useAsDefault= function(x, ...) stats::toeplitz(x),
>>
>> On 2023-06-13 8:01 pm, Simon Urbanek wrote:
>>> I agree that this is not an R issue, but rather user error of not 
>>> defining a proper generic so the check is right. Obviously, defining 
>>> a generic with implementation-specific ncol default makes no sense 
>>> at all, it should only be part of the method implementation. If one 
>>> was to implement the same default behavior in the generic itself 
>>> (not necessarily a good idea) the default would be ncol = if 
>>> (complete) nrow(qr.R(qr, TRUE)) else min(dim(qr.R(qr, TRUE))) to not 
>>> rely on the internals of the implementation.
>>>
>>> Cheers,
>>> Simon
>>>
>>>
>>>> On 14/06/2023, at 6:03 AM, Kasper Daniel Hansen 
>>>> <kasperdanielhansen using gmail.com> wrote:
>>>>
>>>> On Sat, Jun 3, 2023 at 11:51 AM Mikael Jagan <jaganmn2 using gmail.com> 
>>>> wrote:
>>>>
>>>>> The formals of the newly generic 'qr.X' are inherited from the 
>>>>> non-generic
>>>>> function in the base namespace.  Notably, the inherited default 
>>>>> value of
>>>>> formal argument 'ncol' relies on lazy evaluation:
>>>>>
>>>>>> formals(qr.X)[["ncol"]]
>>>>>      if (complete) nrow(R) else min(dim(R))
>>>>>
>>>>> where 'R' must be defined in the body of any method that might 
>>>>> evaluate
>>>>> 'ncol'.
>>>>>
>>>>
>>>> Perhaps I am misunderstanding something, but I think Mikael's 
>>>> expectations
>>>> about the scoping rules of R are wrong.  The enclosing environment 
>>>> of ncol
>>>> is where it was _defined_ not where it is _called_ (apologies if I am
>>>> messing up the computer science terminology here).
>>>>
>>>> This suggests to me that codetools is right.  But a more extended 
>>>> example
>>>> would be useful. Perhaps there is something special with setOldClass()
>>>> which I am no aware of.
>>>>
>>>> Also, Bioconductor has 100s of packages with S4 where codetools 
>>>> works well.
>>>>
>>>> Kasper
>>>>
>>>>     [[alternative HTML version deleted]]
>>>>
>>>> ______________________________________________
>>>> R-devel using r-project.org mailing list
>>>> https://stat.ethz.ch/mailman/listinfo/r-devel
>>>>
>>>
>>
>> ______________________________________________
>> R-devel using r-project.org mailing list
>> https://stat.ethz.ch/mailman/listinfo/r-devel
> -- 
> Hervé Pagès
>
> Bioconductor Core Team
> hpages.on.github using gmail.com

-- 
Hervé Pagès

Bioconductor Core Team
hpages.on.github using gmail.com

	[[alternative HTML version deleted]]



More information about the R-devel mailing list