From hpages at fhcrc.org  Thu Apr 10 04:16:25 2008
From: hpages at fhcrc.org (Herve Pages)
Date: Wed, 09 Apr 2008 19:16:25 -0700
Subject: [Rd] coerce methods and inheritance
In-Reply-To: <47FD412E.1010609@r-project.org>
References: <47FD11E6.1090901@fhcrc.org> <47FD412E.1010609@r-project.org>
Message-ID: <47FD7879.7080908@fhcrc.org>

Hi John,

John Chambers wrote:
> Herve Pages wrote:
>> Hi,
>>
>> It doesn't seem that the dispatching algo is finding my coerce method 
>> under
>> some circumstances.
>> Let's say I have 2 classes, A and AA and that AA is just a direct 
>> extension
>> of A with no additional slots:
>>
>>    setClass("A", representation(ii="integer"))
>>    setClass("AA", contains="A")
>>
>> I can define a method for coercing my objects to an integer vector with:
>>
>>    setAs("A", "integer", function(from) {cat("I'm the A->integer 
>> coerce method\n"); from at ii})
>>
>> and this works as expected when my object is an AA instance:
>>
>>    > aa <- new("AA", ii=sample(10, 5))
>>    > as(aa, "integer")
>>    I'm the A->integer coerce method
>>    [1] 10  1  6  4  7
>>
>> But things don't behave that way anymore if I introduce a direct 
>> extension of AA:
>>
>>    setClass("OrderedAA",
>>      contains="AA",
>>      validity=function(object)
>>      {
>>          if (!all(diff(object at ii) >= 0))
>>              return("slot 'ii' is not ordered")
>>          TRUE
>>      }
>>    )
>>
>> and a method for coercing an A object to an OrderedAA object:
>>
>>    setAs("A", "OrderedAA",
>>      function(from)
>>      {
>>          cat("I'm the A->OrderedAA coerce method\n")
>>          new("OrderedAA", ii=sort(from at ii))
>>      }
>>    )
>>
>> My A->OrderedAA coerce method is not called anymore:
>>
>>    > oaa <- as(aa, "OrderedAA")
>>    > oaa
>>    > validObject(oaa)
>>    Error in validObject(oaa) :
>>      invalid class "OrderedAA" object: slot 'ii' is not ordered
>>
>> This looks like a bug to me.
>>   
> Well, obscure perhaps, and not as well documented as it might be.
> 
> Defining a subclass of "AA" creates implicit coerce methods in both 
> directions.  The method from "AA" to its subclass creates a new object 
> from the subclass, then inserts the inherited slots.
> 
>  > selectMethod("coerce", c("AA", "OrderedAA"))
> Method Definition:
> 
> function (from, to)
> {
>    obj <- new("OrderedAA")
>    as(obj, "AA") <- from
>    obj
> }

The problem is that this implicit method doesn't seem to check the validity
of the new object. So now my users have an easy way to create broken objects
without being told that they are doing something wrong... unless I have
redefined a lot of coerce methods in my software (and there can be a lot of
them).
Unfortunately for me and my project, it looks like most of these "implicit"
methods are doing the wrong thing. So if the purpose of having them was to
make the developer's life easier, it doesn't work for me.

> 
> Signatures:
>        from to        target  "AA" "OrderedAA"
> defined "AA" "OrderedAA"
> 
> The situation is made more confusing because these methods are only 
> explicitly inserted in the coerce() function the first time they're used 
> (for obvious efficiency reasons).

Even worse, after I define my coerce method for A->OrderedAA, and _before_
I try to coerce my first AA object to an OrderedAA object, I get this:

   > selectMethod("coerce", c("AA", "OrderedAA"))
   Method Definition:

   function (from, to = "OrderedAA", strict = TRUE)
   {
     cat("I'm the A->OrderedAA coerce method\n")
     new("OrderedAA", ii = sort(from at ii))
   }

   Signatures:
           from to
   target  "AA" "OrderedAA"
   defined "A"  "OrderedAA"

which is not reporting the truth (the method that will actually be selected
will be the implicit one, not mine).

> 
> Notice that this is a direct method, not an inherited one.  It will be 
> chosen by the method selection from as().
> 
> So it is true that if you want to override the implicit methods, you 
> have to do that for each new subclass, presumably when defining the 
> subclass.
> 
>  >    setAs("AA", "OrderedAA",
> +      function(from)
> +      {
> +          cat("I'm the A->OrderedAA coerce method\n")
> +          new("OrderedAA", ii=sor .... [TRUNCATED]
>  > as(aa, "OrderedAA")
> I'm the A->OrderedAA coerce method
> An object of class "OrderedAA"
> Slot "ii":
> [1]  4  5  7  8 10
> 
> It's possible to argue that an inherited, explicitly defined method, as 
> in your case, is worth more than a direct, implicitly defined method.

It's definitely worth more than a direct, implicitly defined method that
produces invalid objects ;-)

Anyway I can see why it can be nice (and save some efforts to the developer)
to have a coercion mechanism that works out-of-the-box, but I'm wondering
whether this is that useful to have implicit coerce methods in _both_
directions. It seems to me that, most of the times, the parent-to-child
direction will do the wrong thing, because, generally speaking, you need
some extra information (the extra slots) to coerce from the parent class
to the child class.

Thanks for the clarification!

H.

> But whether this would be true in all applications is not obvious.
> 
> But that the documentation needs to spell this out--no question.  Thanks 
> for bringing it up.
> 
> John
> 
> 
>> Thanks,
>> H.
>>
>> ______________________________________________
>> R-devel at r-project.org mailing list
>> https://stat.ethz.ch/mailman/listinfo/r-devel
>>
>>   
>



[Rd] coerce methods and inheritance
Herve Pages