Skip to content

as(1:4, "numeric") versus as.numeric(1:4, "numeric")

6 messages · John Chambers, Hervé Pagès

#
Hi John,
John Chambers wrote:
Yes indeed. From a fresh start:

 > invisible(selectMethod("coerce", c("integer","numeric")))
 > class(as(1:4, "numeric"))
[1] "numeric"

But without the initial call to selectMethod(), as(1:4, "numeric")
returns an integer vector.

Sorry but it's hard for me to understand the reasons for having
such behaviour, especially when selectMethod() is described as a
function "to *look* for a method corresponding to a given generic
function and signature". Apparently it does more than just looking...
So one problem seems to be that, on a fresh start, *both*
     as(1:4, "numeric")
and
     selectMethod("coerce", c("integer", "numeric"))
will cache a coerce method for the c("integer", "numeric") signature,
but they don't cache the *same* method!

The automatic method cached by 'as(1:4, "numeric")' seems to be
coming from:

   getClassDef("integer")@contains$numeric at coerce

Maybe one way to improve things would be to modify this part of
the class definition for "integer" so it is in sync with

   selectMethod("coerce", c("integer", "numeric")).

There are other situations where the coerce methods are not
in sync:

   > getClassDef("factor")@contains$integer at coerce
   function (from, strict = TRUE)
   {
     attributes(from) <- NULL
     from
   }
   <environment: namespace:methods>

   > selectMethod("coerce", c("factor", "integer"))
   Method Definition:

   function (from, to, strict = TRUE)
   {
     value <- as.integer(from)
     if (strict)
         attributes(value) <- NULL
     value
   }
   <environment: namespace:methods>

That isn't a problem here because both methods will produce
the same result but is there any reason why the former
couldn't use the same code as the latter?

A more radical approach would be to have a call to

   selectMethod("coerce", c("integer", "numeric"))

have the same effect on the table of coerce methods than a
call to

   as(1:4, "numeric")

i.e. the former will insert the same automatic method as the
latter. That means that all the hard work made by the as()
function in order to find/create/cache an appropriate method
would need to be moved to selectMethod() so in that function
'f="coerce"' would become a special case.
Then as() would become a 10 line function (or less) that would
basically delegate to selectMethod("coerce", ...) to do the hard
work. This solution seems better to me as it would then guarantee
consistency between what as() does and what
selectMethod("coerce", ...) says.

Cheers,
H.

  
    
#
The point I was making was that as() is not just a synonym for selecting 
a method from coerce() by the usual inheritance rules.  I don't believe 
it should be, and the documentation emphasizes that inheritance is not 
used in the ordinary way.

If one were to start rewriting code (which I'm not suggesting) my 
preference would be to  have coerce() not be a generic function, 
eliminating the offending selectMethod() calls.

John
On 4/1/10 12:31 AM, Herv? Pag?s wrote:
#
John Chambers wrote:
I got this. If you look carefully at the change I'm suggesting for
selectMethod(), you will notice that I said that f="coerce" would
then need to become a special case.
In other words, when f="coerce", the usual inheritance rules are 
replaced by the rules that are currently implemented in as() and
described in its man page.
So to summarize: (1) the code in as() that is currently in charge of
selecting/creating/caching the most adequate coerce method is moved
to selectMethod(), (2) the sections in ?as that describe the rules
of this non-standard inheritance are moved to ?selectMethod.
Then how one would know what as() is doing *exactly* i.e. which
coerce method was used or will be used in such or such situation?
showMethods()/selectMethod() are great tools because they allow the
developer to predict things and troubleshoot.

If you don't like putting the non-standard inheritance rules in
selectMethod() (when f="coerce") then you can always add something
like selectAsMethod() and put them here, and also add something
like showAsMethods(). I guess that's more or less what you are
saying when you propose to have coerce() not be a generic function,
at least not an usual one.
But it's important to expose selectAsMethod()/showAsMethods() to
the user. We absolutely need them!

Now I'm not sure I understand your concern about putting this
stuff in the existing selectMethod()/showMethods(). Why not just
ignore the useInheritance= arg when f="coerce"? Again, this would
be a special case anyway (and documented). The advantage of this
solution (over selectAsMethod()/showAsMethods()) is to avoid having
to introduce and expose 2 new names, so the user doesn't have to
switch between select*/show* tools depending on whether f="coerce"
or not.

H.

  
    
#
The problem that you have exposed is that if one uses the *standard* 
form of selectMethod() on function "coerce", this could corrupt the 
intended set of methods used by as().  Of course, no one was expected to 
do this, but it's not caught or warned (as opposed to a direct call to 
coerce(), which does generate a warning).

If people think this is something of sufficient importance to put high 
on the fix-it list, contributions are welcome as always.

However, it seems a really bad idea to start making the definition of 
method selection by inheritance depend in an arbitrary way on which 
function is operated on.  Documenting what selection does is hard enough 
as it is.

A solution localized to the as() computation is to treat the mechanism 
involved in a call to setAsMethod  as something special, and provide 
whatever information is needed via showAsMethods(), or similar.  From a 
tutorial view, it might be good to emphasize that this is NOT the usual 
method dispatch--indeed, the present discussion supports that view.

Method selection in a functional language is a difficult concept, 
particularly for programmers coming from a standard OOP background.  If 
we're going to change it, let's aim to make it simpler, not more 
complicated.  What about getting rid of the kludgy argument 
useInheritance= in a future version, if nobody has a use for it other 
than in as()?  If you look at the code, you'll see that would simplify 
it significantly, and even speed up selection somewhat. There's a change 
I would be happy about!

John
On 4/1/10 2:59 PM, Herv? Pag?s wrote:
#
One more (final?) note on this example. A simpler change that would 
eliminate the inconsistent result from as():

1.  Change the warning on direct calls to coerce() to an error.

2. Move the caching of inherited methods to the actual dispatch code, so 
selectMethod() itself has no side effect.

The example itself suggests that 1 is needed, since a direct call would 
use ordinary inheritance rules, not those imposed by as().

Unless there is an unnoticed issue with this, I'll make the change, to 
r-devel of course.

John
On 4/1/10 5:07 PM, John Chambers wrote:
#
Hi John,
John Chambers wrote:
The *definition* of method selection is what it is. And it's a fact that
the rules are different for as(). Hence selectMethod("coerce", ...) or
selectAsMethod() should reflect this.
Just to clarify, I'm not proposing to change the *rules* of method
selection. The standard rules would remain exactly the same, and
the special rules used by as() (specified in its man page) would
also remain exactly the same. What would change however is that
selectMethod("coerce", ...) would not be broken anymore i.e. it
would not corrupt the table of "coerce" methods anymore, and it
would return the right method (as specified in the specs).

I hope I made the point that it's not about making things more
complicated ;-)
That sounds all good to me.

Thanks,
H.