From jmc at r-project.org  Sat Oct 21 16:28:36 2006
From: jmc at r-project.org (John Chambers)
Date: Sat, 21 Oct 2006 10:28:36 -0400
Subject: [Rd] default arguments in generics and methods
In-Reply-To: <0D795BA2-D5C6-4A6F-997E-C763905C677C@mac.com>
References: <0D795BA2-D5C6-4A6F-997E-C763905C677C@mac.com>
Message-ID: <453A2E94.3070302@r-project.org>

You're conflating 3 different things here:  how methods are selected, 
how R treats missing arguments, and how the standardGeneric() call is 
implemented.

Maybe more relevant, if this question is motivated by a practical need, 
you may want to rethink what the generic function does.  "Nonstandard" 
generic functions are called that  for a reason.  The concept behind 
methods in S is that a generic function defines the _function_ or 
purpose of the computation, but it's only the methods that define how 
the purpose is carried out.

Using a nonstandard generic that does some extra computation always 
blurs the concept, and often makes methods harder to write or 
understand.  That you've found it necessary to worry about the subtle 
points in your question is a warning sign.  It's usually worth asking 
whether the place that you call standardGeneric() could in fact _be_ the 
generic, with the other computations moved to a nongeneric wrapper 
function.  Having said that,  when the computations depend on missing 
arguments, the situation may be more complicated.

Anyway, the following relate to your question.

1.  In method selection, the "missing" class is used if the C-level 
equivalent of the test missing(ARG) is TRUE when selection takes place.  
On to point 2.

2.  The R evaluator stores a special object corresponding to missing 
arguments.  Missing arguments are treated by the lazy evaluation rules, 
so the exact contents of the current object of this name in the local 
environment will depend on whether there has been a reference to the 
argument.  The missing(ARG) test remains TRUE when the default 
expression is evaluated but becomes FALSE if a direct assignment of ARG 
takes place (just reporting this, not defending it).

3.  standardGeneric() creates a  new context into which it copies 
bindings for all the objects in the frame of the generic function call, 
but tries to maintain defaults for missing arguments.  If you want to 
see how it works, look at the routine R_execMethod in file 
src/main/eval.c of the R source.

The somewhat unfortunate result of all this can be seen by comparing two 
nonstandard generics:

setGeneric("foo", function(x, y){if(missing(y)) y <- x+1; 
standardGeneric("foo")})

setGeneric("foo2", function(x, y = x+1){if(missing(y)) message(y); 
standardGeneric("foo2")})

The "foo" function will never dispatch a method for c("numeric", 
"missing") because y is assigned directly.  The "foo2" function will 
dispatch such a method though, even though the numerical value of y will 
be the same in message(y).

As may be obvious, the computation is subtle and it might be foolish to 
rely on the details as if they were part of the API.

Parlamis Franklin wrote:
> i believe the following is true but would appreciate confirmation  
> that it is intended behavior and will continue:
>
> if a default argument is employed in the definition of a generic  
> function, and the generic is called with the argument in question  
> (call it 'ARG') missing, then the method for signature (..., ARG =  
> "missing", ...) will be called by 'standardGeneric' whether or not  
> ARG is evaluated in the body of the generic prior to the call to  
> 'standardGeneric';  however, the value assigned to ARG in the method  
> call will depend on whether ARG was evaluated in the body of the  
> generic prior to the call to 'standardGeneric' (with the default  
> value assigned in the case of prior evaluation and no value assigned  
> in the case of no prior evaluation).  furthermore, if ARG is  
> explicitly assigned to in the body of the generic prior to the call  
> to 'standardGeneric', then the method for signature (..., ARG = class 
> (ARG), ...) will be called, with the assigned value of ARG passed to  
> the call.
>
> this behavior is exhibited by the code below:
>
> __
>
> setGeneric("foo", function(x = 1) standardGeneric("foo"),  
> useAsDefault = FALSE)
> setMethod("foo", signature(x = "missing"), function(x = 3) x)
>
> setGeneric("foo2", function(x = 1) {x; standardGeneric("foo2")},  
> useAsDefault = FALSE)
> setMethod("foo2", signature(x = "missing"), function(x = 3) x)
>
> setGeneric("foo3", function(x = 1) {x<-2; standardGeneric("foo3")},  
> useAsDefault = FALSE)
> setMethod("foo3", signature(x = "missing"), function(x = 3) x)
> setMethod("foo3", signature(x = "numeric"), function(x = 3) x^2)
>
> foo()		# returns 3
> foo2()		# returns 1
> foo3()		# returns 4
>
> ___
>
> franklin parlamis
>
> ______________________________________________
> R-devel at r-project.org mailing list
> https://stat.ethz.ch/mailman/listinfo/r-devel
>
>




[Rd] default arguments in generics and methods
John Chambers