From jmc at r-project.org  Thu Apr 10 17:55:27 2008
From: jmc at r-project.org (John Chambers)
Date: Thu, 10 Apr 2008 08:55:27 -0700
Subject: [Rd] coerce methods and inheritance
In-Reply-To: <47FD7879.7080908@fhcrc.org>
References: <47FD11E6.1090901@fhcrc.org> <47FD412E.1010609@r-project.org>
	<47FD7879.7080908@fhcrc.org>
Message-ID: <47FE386F.5050205@r-project.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.
Yes, I understand that.  But does that mean that all other applications 
should NOT have the automatic replacement?  I'm not hugely committed to 
it but notice that the real issue is the underlying
  as(obj, "AA") <- from
which uses a replacement method generated for simple contained 
relationships.  Throwing that out to save you having to write a coerce 
method seems a little drastic.

The underlying assumption is that most classes are defined by the 
classes of their slots. Your example has extra, implicit requirements, 
which is fine but is not likely to come for free.
>
>>
>> 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
John Chambers