[Rd] as.list fails on functions with S3 classes

Martin Maechler m@ech|er @end|ng |rom @t@t@m@th@ethz@ch
Thu Apr 29 09:09:49 CEST 2021


>>>>> brodie gaslam via R-devel 
>>>>>     on Thu, 29 Apr 2021 01:04:01 +0000 (UTC) writes:

    >> On Wednesday, April 28, 2021, 5:16:20 PM EDT, Gabriel Becker <gabembecker using gmail.com> wrote:
    >> 
    >> Hi Antoine,
    >> 
    >> I would say this is the correct behavior. S3 dispatch is solely (so far as
    >> I know?) concerned with the "actual classes" on the object. This is because
    >> S3 classes act as labels that inform dispatch what, and in what order,
    >> methods should be applied. You took the function class (ie label) off of
    >> your object, which means that in the S3 sense, that object is no longer a
    >> function and dispatching to function methods for it would be incorrect.
    >> This is independent of whether the object is still callable "as a function".
    >> 
    >> The analogous case for non-closures to what you are describing would be for
    >> S3 to check mode(x) after striking out with class(x) to find relevant
    >> methods. I don't think that would be appropriate.

    > I would think of the general case to be to check `class(unclass(x))` on
    > strike-out.  This would then include things such as "matrix", etc.
    > Dispatching on the implicit class as fallback seems like a natural thing
    > to do in a language that dispatches on implicit class when there is none.
    > After all, once you've struck out of your explicit classes, you have
    > none left!

    > This does happen naturally in some places (e.g. interacting with a
    > data.frame as a list), and is quite delightful (usually).  I won't get
    > into an argument of what the documentation states or whether any changes
    > should be made, but to me that dispatch doesn't end with the implicit
    > class seems feels like a logical wrinkle.  Yes, I can twist my brain to
    > see how it can be made to make sense, but I don't like it.

    > A fun past conversation on this very topic:

    > https://stat.ethz.ch/pipermail/r-devel/2019-March/077457.html

Thank you, Gabe and Brodie.

To the OP,  Gabe's advice to *NOT* throw away an existing class
is really important,  and  good code -- several examples in base R --
would really *extend* a class in such cases, i.e.,

function(x, ...) {
     ......
     ans <- things.on(x, .....)
     class(ans) <- c("foo", class(x))   #
     ans
}

I don't have time to go in-depth here (teaching and other duties),
but  I want to point you to one important extra point,
which I think you have not been aware:

S3 dispatch *does* look at what you see from class()  *but* has
always done some extra things, notably for atomic and other
*base* objects.  There's always been a dedicated function in R's C
code to do this,  R_data_class2(),  e.g., called from  C
usemethod() called from R's UseMethod().

Since R 4.0.0,  we have provided R function .class2()   to give
the same result as the internal R_data_class2(),  and hence
show the classes (in the correct order!) which are really for S3
dispatch.

The NEWS entry for that was

      \item New function \code{.class2()} provides the full character
      vector of class names used for S3 method dispatch.


Best,
Martin


    > Best,

    > B.

    >> Also, as an aside, if you want your class to override methods that exist
    >> for function you would want to set the class to c("foo", "function"), not
    >> c("function", "foo"), as you had it in your example.
    >> 
    >> Best,
    >> ~G
    >> 
    >> On Wed, Apr 28, 2021 at 1:45 PM Antoine Fabri <antoine.fabri using gmail.com>
    >> wrote:
    >> 
    >>> Dear R devel,
    >>> 
    >>> as.list() can be used on functions, but not if they have a S3 class that
    >>> doesn't include "function".
    >>> 
    >>> See below :
    >>> 
    >>> ```r
    >>> add1 <- function(x) x+1
    >>> 
    >>> as.list(add1)
    >>> #> $x
    >>> #>
    >>> #>
    >>> #> [[2]]
    >>> #> x + 1
    >>> 
    >>> class(add1) <- c("function", "foo")
    >>> 
    >>> as.list(add1)
    >>> #> $x
    >>> #>
    >>> #>
    >>> #> [[2]]
    >>> #> x + 1
    >>> 
    >>> class(add1) <- "foo"
    >>> 
    >>> as.list(add1)
    >>> #> Error in as.vector(x, "list"): cannot coerce type 'closure' to vector of
    >>> type 'list'
    >>> 
    >>> as.list.function(add1)
    >>> #> $x
    >>> #>
    >>> #>
    >>> #> [[2]]
    >>> #> x + 1
    >>> ```
    >>> 
    >>> In failing case the argument is dispatched to as.list.default instead of
    >>> as.list.function.
    >>> 
    >>> (1) Shouldn't it be dispatched to as.list.function ?
    >>> 
    >>> (2) Shouldn't all generics when applied on an object of type closure fall
    >>> back to the `fun.function` method  before falling back to the `fun.default`
    >>> method ?
    >>> 
    >>> Best regards,
    >>> 
    >>> Antoine

    > ______________________________________________
    > R-devel using r-project.org mailing list
    > https://stat.ethz.ch/mailman/listinfo/r-devel



More information about the R-devel mailing list