Why, for an integer vector x, does as(x, “numeric”) trigger loading of an additional S4 method for coerce?

亡梦爱人 提交于 2019-11-30 06:03:57

I'm not sure whether I can answer your question exhaustively, but I'll try.

The help of the as() function states:

The function ‘as’ turns ‘object’ into an object of class ‘Class’. In doing so, it applies a “coerce method”, using S4 classes and methods, but in a somewhat special way.

[...]

Assuming the ‘object’ is not already of the desired class, ‘as’ first looks for a method in the table of methods for the function 'coerce’ for the signature ‘c(from = class(object), to = Class)’, in the same way method selection would do its initial lookup.

[...]

If no method is found, ‘as’ looks for one. First, if either ‘Class’ or ‘class(object)’ is a superclass of the other, the class definition will contain the information needed to construct a coerce method. In the usual case that the subclass contains the superclass (i.e., has all its slots), the method is constructed either by extracting or replacing the inherited slots.

This is exactly what you can see if you look at the code of the as() function (to see it, type as (without the parentheses!) to the R console) - see below. First it looks for an asMethod, if it can't find any it tries to construct one, and finally at the end it executes it:

if (strict) 
    asMethod(object)
else asMethod(object, strict = FALSE)

When you copy-paste the code of the as() function and define your own function - let's call it myas() - your can insert a print(asMethod) above the if (strict) just mentioned to get the function used for coercing. In this case the output is:

> myas(4L, 'numeric')
function (from, to = "numeric", strict = TRUE) 
if (strict) {
    class(from) <- "numeric"
    from
} else from
<environment: namespace:methods>
attr(,"target")
An object of class “signature”
     from        to 
"integer" "numeric" 
attr(,"defined")
An object of class “signature”
     from        to 
"integer" "numeric" 
attr(,"generic")
[1] "coerce"
attr(,"generic")attr(,"package")
[1] "methods"
attr(,"class")
[1] "MethodDefinition"
attr(,"class")attr(,"package")
[1] "methods"
attr(,"source")
[1] "function (from, to = \"numeric\", strict = TRUE) "
[2] "if (strict) {"                                    
[3] "    class(from) <- \"numeric\""                   
[4] "    from"                                         
[5] "} else from"                                      
[1] 4

So, as you can see (look at attr(,"source")), the as(4L, 'numeric') simply assigns class numeric to the input object and returns it. Thus, the following two snippets are equivalent (for this case!):

> # Snippet 1
> x = 4L
> x = as(x, 'numeric')

> # Snippet 2
> x = 4L
> class(x) <- 'numeric'

Interestingly, both to 'nothing'. More interestingly, the other way round it works:

> x = 4
> class(x)
[1] "numeric"
> class(x) <- 'integer'
> class(x)
[1] "integer"

I'm not exactly sure about this (as the class method seems to be implemented in C) - but my guess would be that when assigning class numeric, it first checks whether it is already numeric. Which could be the case as integer is numeric (although not double) - see also the "historical anomaly" quote below:

> x = 4L
> class(x)
[1] "integer"
> is.numeric(x)
[1] TRUE

Regarding as.numeric: This is a generic method and calls as.double(), which is why it 'works' (from the R help on as.numeric):

It is a historical anomaly that R has two names for its floating-point vectors, ‘double’ and ‘numeric’ (and formerly had ‘real’).

‘double’ is the name of the type. ‘numeric’ is the name of the mode and also of the implicit class.

Regarding questions (1) - (3): The magic happens in those four lines at the top of the as function:

where <- .classEnv(thisClass, mustFind = FALSE)
coerceFun <- getGeneric("coerce", where = where)
coerceMethods <- .getMethodsTable(coerceFun, environment(coerceFun), inherited = TRUE)
asMethod <- .quickCoerceSelect(thisClass, Class, coerceFun, coerceMethods, where)

Im lacking the time to dig into there, sorry.

Hope that helps.

Looking at the source code for as(), it has two parts. (The source code has been shortened for clarity). First, it looks for existing methods for coerce(), as you described above.

function (object, Class, strict = TRUE, ext = possibleExtends(thisClass, 
    Class)) 
{
    thisClass <- .class1(object)
    where <- .classEnv(thisClass, mustFind = FALSE)
    coerceFun <- getGeneric("coerce", where = where)
    coerceMethods <- .getMethodsTable(coerceFun, environment(coerceFun), 
        inherited = TRUE)
    asMethod <- .quickCoerceSelect(thisClass, Class, coerceFun, 
        coerceMethods, where)

    # No matching signatures from the coerce table!!!
    if (is.null(asMethod)) {
        sig <- c(from = thisClass, to = Class)
        asMethod <- selectMethod("coerce", sig, optional = TRUE, 
            useInherited = FALSE, fdef = coerceFun, mlist = getMethodsForDispatch(coerceFun))

If it doesn't find any methods, as in this case, then it attempts to create a new method as follows:

        if (is.null(asMethod)) {
            canCache <- TRUE
            inherited <- FALSE

            # The integer vector is numeric!!!
            if (is(object, Class)) {
                ClassDef <- getClassDef(Class, where)
                if (identical(ext, FALSE)) {}
                else if (identical(ext, TRUE)) {}
                else {
                  test <- ext@test

                  # Create S4 coercion method here
                  asMethod <- .makeAsMethod(ext@coerce, ext@simple, 
                    Class, ClassDef, where)
                  canCache <- (!is(test, "function")) || identical(body(test), 
                    TRUE)
                }
            }
            if (is.null(asMethod)) {}
            else if (canCache) 
                asMethod <- .asCoerceMethod(asMethod, thisClass, 
                  ClassDef, FALSE, where)
            if (is.null(asMethod)) {}
            else if (canCache) {
                cacheMethod("coerce", sig, asMethod, fdef = coerceFun, 
                  inherited = inherited)
            }
        }
    }

    # Use newly created method on object here
    if (strict) 
        asMethod(object)
    else asMethod(object, strict = FALSE)

By the way, if you're only dealing with the basic atomic types, I would stick to base functions and avoid the methods package; the only reason to use methods is dealing with S4 objects.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!