as.list fails on functions with S3 classes

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

as.list fails on functions with S3 classes

Antoine Fabri
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

        [[alternative HTML version deleted]]

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

Re: as.list fails on functions with S3 classes

Gabriel Becker-2
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.

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 <[hidden email]>
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
>
>         [[alternative HTML version deleted]]
>
> ______________________________________________
> [hidden email] mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel
>

        [[alternative HTML version deleted]]

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

Re: as.list fails on functions with S3 classes

R devel mailing list

> On Wednesday, April 28, 2021, 5:16:20 PM EDT, Gabriel Becker <[hidden email]> 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

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 <[hidden email]>
> 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

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

Re: as.list fails on functions with S3 classes

Gabriel Becker-2
On Wed, Apr 28, 2021 at 6:04 PM brodie gaslam <[hidden email]>
wrote:

>
> > On Wednesday, April 28, 2021, 5:16:20 PM EDT, Gabriel Becker <
> [hidden email]> wrote:
> >
>
> > 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.


To me the general case is writing a robust default method that covers
whatever would be class(unclass(x)) would be. When you give an object a new
S3 class, you have the option of extending (c("newclass", "oldclass")) and
"not extending" (just "newclass"), and it certainly doesn't seem to me that
these two should behave the same. Perhaps others disagree.


>   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).


So I don't know of any places that this happens *in the S3 dispatch sense*.
There are certainly places where the default  method supports lists, and if
data.frame doesn't have a method so it hits the default method, which
handles lists. Am I missing somewhere where the dispatch gives a data.frame
to a list method (in S3 space)?


> 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.
>

I suppose it depends on how you view S3 dispatch. To me, view it purely as
labeling. S3 dispatch has literally nothing to do with the content of the
object. What you're describing would make that not the case. (Or if I'm
wrong about what is happening, then I'm incorrect about that too).

Best,
~G


>
> A fun past conversation on this very topic:
>
> https://stat.ethz.ch/pipermail/r-devel/2019-March/077457.html
>
> 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 <[hidden email]>
> > 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
>

        [[alternative HTML version deleted]]

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

Re: as.list fails on functions with S3 classes

Martin Maechler
In reply to this post by R devel mailing list
>>>>> 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 <[hidden email]> 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 <[hidden email]>
    >> 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

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

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