From jmc at r-project.org  Thu Jan 29 19:06:43 2015
From: jmc at r-project.org (John Chambers)
Date: Thu, 29 Jan 2015 10:06:43 -0800
Subject: [Rd] [Q] Get formal arguments of my implemented S4 method
In-Reply-To: <CAOQ5NyeHO4YxgmWFrk+Ov_mEFc-r_eo3M4mKeAnQdBo=n53=WQ@mail.gmail.com>
References: <D0EE7317.B896E%proebuck@mdanderson.org>
	<CAOQ5NycvSXb4OQcO_rdWYpFz-5gLWepJHa+jT6MqDBM6xt=FkQ@mail.gmail.com>
	<D0EE7C23.B8A11%proebuck@mdanderson.org>
	<CAOQ5NyenjOHjGt+hnFGa2mMnhUN34weFN1kxrLKaG=LrbWpuJg@mail.gmail.com>
	<4D044AA9-AD74-4A6A-9D43-69C7961BE2D2@r-project.org>
	<CABdHhvHRO2+tP+hybZx9csEts0kEiu-3k2e9AYv6NZwHWJpt=w@mail.gmail.com>
	<CAF8bMcZGVWdtGgLwbOkKmMy_2xXB9-oMOHLQaAjojmnAViDyzQ@mail.gmail.com>
	<B7ED848B-E850-4E92-AE72-489BE6D1162E@r-project.org>
	<CAOQ5NyeHO4YxgmWFrk+Ov_mEFc-r_eo3M4mKeAnQdBo=n53=WQ@mail.gmail.com>
Message-ID: <681FA277-C657-4F31-970F-1B2EF7882B1C@r-project.org>

Some experimenting is needed.  But I think a subclass is likely to be cleaner.  The official model is that methods and generic differ only in the body, so having an object-based way to say that some methods are non-conforming feels more natural to me.

On Jan 29, 2015, at 9:57 AM, Michael Lawrence <lawrence.michael at gene.com> wrote:

> Would we really need the special class or would simply checking the formals
> of the method against those of the generic be simple and fast enough?
> 
> On Thu, Jan 29, 2015 at 9:41 AM, John Chambers <jmc at r-project.org> wrote:
> 
>> I wouldn't want to add more to the current approach; if someone would like
>> to devote some time, the much preferable idea IMO would be to replace the
>> whole mechanism.
>> 
>> Here's one suggestion:
>> 
>> 1.  have a class, say "nonConformingMethod" for method definitions that
>> diverge in the argument list.
>> 
>> 2. the internal dispatch code checks the class of the selected definition
>> (this can likely be done with little cost in the standard case).   In the
>> case of non-conforming, the arguments are rematched to define the method's
>> other arguments.
>> 
>> The possibilities need examining, but my feeling is that the re-matching
>> should happen in the current frame, as opposed to doing a new call.
>> 
>> There is a fair amount of code, for example in callNextMethod, that
>> requires some computations using knowledge of the current mechanism.  If at
>> some point we required re-installing all packages using non-conforming
>> methods, that code could be made simpler and faster.
>> 
>> John
>> 
>> 
>> 
>> 
>> On Jan 29, 2015, at 8:08 AM, William Dunlap <wdunlap at tibco.com> wrote:
>> 
>>> I wish it didn't have to depend on the name '.local'.
>>> 
>>> Back when I wrote a lot of S4 methods I avoided the auto-generated .local
>>> and named the local function something that made sense so that is was
>> easier
>>> for a user to track down the source of an error.
>>> 
>>> E.g., define the generic QQQ with numeric and integer methods:
>>> setGeneric("QQQ",
>>>           function(x, ...)NULL)
>>> setMethod("QQQ",
>>>          signature=signature(x="numeric"),
>>>          function(x, lower, ...) {
>>>              if (x<lower) stop("x<lower")
>>>          })
>>> setMethod("QQQ",
>>>          signature=signature(x="integer"),
>>>          function(x, ...) {
>>>              .QQQ.integer <- function(x, lower, ...) if (x<lower)
>> stop("x<lower")
>>>              .QQQ.integer(x, ...)
>>>          })
>>> and try using them:
>>>> QQQ(3.4, 10)
>>>  Error in .local(x, ...) : x<lower
>>>> traceback()
>>>  4: stop("x<lower") at #4
>>>  3: .local(x, ...)
>>>  2: QQQ(3.4, 10)
>>>  1: QQQ(3.4, 10)
>>>> QQQ(3L, 10)
>>>  Error in .QQQ.integer(x, ...) : x<lower
>>>> traceback()
>>>  4: stop("x<lower") at #4
>>>  3: .QQQ.integer(x, ...) at #5
>>>  2: QQQ(3L, 10)
>>>  1: QQQ(3L, 10)
>>> I think the latter gives the user more guidance on how to fix the
>> problem.
>>> 
>>> Perhaps instead of searching for an assignment to '.local' you could
>>> search for an assignment to the name of the function used in the last
>>> function call of the method.
>>> 
>>> 
>>> 
>>> Bill Dunlap
>>> TIBCO Software
>>> wdunlap tibco.com
>>> 
>>> On Thu, Jan 29, 2015 at 6:34 AM, Hadley Wickham <h.wickham at gmail.com>
>> wrote:
>>> On Thu, Jan 29, 2015 at 7:57 AM, John Chambers <jmc at r-project.org>
>> wrote:
>>>> 
>>>> On Jan 28, 2015, at 6:37 PM, Michael Lawrence <
>> lawrence.michael at gene.com> wrote:
>>>> 
>>>>> At this point I would just due:
>>>>> 
>>>>> formals(body(method)[[2L]])
>>>>> 
>>>>> At some point we need to figure out what to do with this .local()
>> confusion.
>>>> 
>>>> Agreed, definitely.  The current hack is to avoid re-matching
>> arguments on method dispatch, so a fix would need to be fairly deep in the
>> implementation.
>>>> 
>>>> But I don't think the expression above is quite right.
>> body(method)[[2L]] is the assignment.  You need to evaluate the rhs.
>>>> 
>>>> Here is a function that does the same sort of thing, and returns the
>> standard formals for the generic if this method does not have nonstandard
>> arguments.  We should probably add a version of this function for 3.3.0, so
>> user code doesn't have hacks around the current hack.
>>>> 
>>>> methodFormals <- function(f, signature = character()) {
>>>>    fdef <- getGeneric(f)
>>>>    method <- selectMethod(fdef, signature)
>>>>    genFormals <- base::formals(fdef)
>>>>    b <- body(method)
>>>>    if(is(b, "{") && is(b[[2]], "<-") && identical(b[[2]][[2]],
>> as.name(".local"))) {
>>>>        local <- eval(b[[2]][[3]])
>>>>        if(is.function(local))
>>>>            return(formals(local))
>>>>        warning("Expected a .local assignment to be a function.
>> Corrupted method?")
>>>>    }
>>>>    genFormals
>>>> }
>>> 
>>> I have similar code in roxygen2:
>>> 
>>> # When a generic has ... and a method adds new arguments, the S4 method
>>> # wraps the definition inside another function which has the same
>> arguments
>>> # as the generic. This function figures out if that's the case, and
>> extracts
>>> # the original function if so.
>>> #
>>> # It's based on expression processing based on the structure of the
>>> # constructed method which looks like:
>>> #
>>> # function (x, ...) {
>>> #   .local <- function (x, ..., y = 7) {}
>>> #   .local(x, ...)
>>> # }
>>> extract_method_fun <- function(x) {
>>>  fun <- x at .Data
>>> 
>>>  method_body <- body(fun)
>>>  if (!is.call(method_body)) return(fun)
>>>  if (!identical(method_body[[1]], quote(`{`))) return(fun)
>>> 
>>>  first_line <- method_body[[2]]
>>>  if (!is.call(first_line)) return(fun)
>>>  if (!identical(first_line[[1]], quote(`<-`))) return(fun)
>>>  if (!identical(first_line[[2]], quote(`.local`))) return(fun)
>>> 
>>>  first_line[[3]]
>>> }
>>> 
>>> 
>>> --
>>> http://had.co.nz/
>>> 
>>> ______________________________________________
>>> R-devel at r-project.org mailing list
>>> https://stat.ethz.ch/mailman/listinfo/r-devel
>>> 
>> 
>> 
>>        [[alternative HTML version deleted]]
>> 
>> ______________________________________________
>> R-devel at r-project.org mailing list
>> https://stat.ethz.ch/mailman/listinfo/r-devel
>> 
> 
> 	[[alternative HTML version deleted]]
> 
> ______________________________________________
> R-devel at r-project.org mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel



[Rd] [Q] Get formal arguments of my implemented S4 method
John Chambers