From bill at insightful.com  Wed Jan 25 21:37:06 2006
From: bill at insightful.com (bill@insightful.com)
Date: Wed, 25 Jan 2006 12:37:06 -0800 (PST)
Subject: [Rd] Using substitute from inside an S4 method
In-Reply-To: <m2ek2wkrpr.fsf@ziti.local>
References: <m2ek2wkrpr.fsf@ziti.local>
Message-ID: <Pine.GSO.4.56.0601251221110.20987@durian.statsci.com>

On Wed, 25 Jan 2006, Seth Falcon wrote:

> I would like to access the name of a variable passed to an S4 method.
> For a function, I would do this:
>
>     f <- function(x) as.character(substitute(x))
>
> This also works for a the examples I have tried for methods that do
> not have extra, non-dispatch args:
>
>     setGeneric("A", function(x, ...) standardGeneric("A"))
>
>     setMethod("A", signature(x="character"),
>               function(x) as.character(substitute(x)))
>
> However, I'm seeing strange behavior if the method uses an extra
> argument:
>
>     setMethod("A", signature(x="numeric"),
>               function(x, y) as.character(substitute(x)))
>
>     num <- 1
>
>     A(num)
>     [1] "x"
>
>     A(num, 2)
>     [1] "x"
>
> Is there a way to make this work?  I came up with one workaround that
> uses a non-standard generic (see below).
>
> It seems that when a method uses extra args matching '...' in the
> generic, an extra frame is used in the evaluation and so substitute()
> isn't reaching the same place as without extra args.

The reason you get an extra frame is that when the method's
argument doesn't match the generic's, setMethod makes a function
with the generic's argument list that calls your method (renamed
".local") and makes that new function the real method.  E.g.,

   > setMethod("A",sig=signature(x="character"), function(x,n){
          if(nchar(x)>n) stop("nchar(x)>n")
          deparse(substitute(x))
     })
   [1] "A"
   > getMethod("A",sig=signature(x="character"))
   Method Definition:

   function (x, ...)
   {
       .local <- function (x, n)
       {
           if (nchar(x) > n)
               stop("nchar(x)>n")
           deparse(substitute(x))
       }
       .local(x, ...)
   }
   ...

This has 2 bothersome side effects.  One is yours:
   > A(paste("One","Two"), 10)
   [1] "x"
and the other is that the function mentioned in the
error report is misleading:
   > A("xyz", 1)
   Error in .local(x, ...) : nchar(x)>n
You can workaround both problems by making a method
that looks somewhat like the the one generated by
setMethod but gets some details right for your
function.  E.g.,

   > setMethod("A",sig=signature(x="character"),
          function(x, ...) {
             A.character <- function(x,n,x.name){
                 if(nchar(x)>n) stop("nchar(x)>n")
                 x.name
             }
             x.name <- deparse(substitute(x))
             A.character(x, ..., x.name=x.name)
          }
     )

This gives a suggestive function name in the error
message
   > A(paste("One","Two"), 3)
   Error in A.character(x, ..., x.name = x.name) :
        nchar(x)>n
and lets you use substitute:
   > A(paste("One","Two"), 10)
   [1] "paste(\"One\", \"Two\")"
Thus you don't have to guess how many frames or environments
lie between your method and the generic.

This works in R and Splus.

----------------------------------------------------------------------------
Bill Dunlap
Insightful Corporation
bill at insightful dot com
360-428-8146

 "All statements in this message represent the opinions of the author and do
 not necessarily reflect Insightful Corporation policy or position."



[Rd] Using substitute from inside an S4 method
bill@insightful.com