"redefineGeneric" =
## redefine the generic function named `f' to have `def' as its function definition.
## This definition will usually be stored as an ordinary function with this name;
## it is also part of the metadata definition of `f' as a generic function. Calling
## the present function re-defines both. That's why the redefinition is tricky enough
## to have its own utility function to carry out the computations.
##
## If `where' is included, modify only the methods definition on database `where'; otherwise,
## modify all definitions on the current search list.
##
## Note that this modifies only the function itself, not any of its methods. By default,
## the argument list must remain the same. If `force' is `TRUE', however, the argument
## list may be changed; if it is, then any existing arguments on the database are respecified
## (which will cause them to become calls to the previous definitions). You will usually
## want to revise these definitions.
##
## The redefinition of the function definition of a generic is a pretty rare event,
## but when it's needed it is non-trivial and should be done through `resetGenericDef', not
## by hand.
function(f, def, where = findMethods(f), force = FALSE)
{
if(length(where) != 1) {
if(length(where) == 0)
warning("no generic definition of \"", f, "\" found")
ans = F
for(what in where)
ans = ans | Recall(f, def, what, force)
return(ans)
}
## eliminate some warning messages, e.g., when assigning function object
opt = options(conflicts.ok = T)
on.exit(options(opt))
methods = getMethods(f, where = where)
oldDef = methods@genericDef
if(identical(oldDef, def))
return(F)
methods@genericDef = def
if(!identical(formalArgs(oldDef), formalArgs(def))) {
if(!force) {
warning("argument lists of old, new definition of \"", f,
"\" on database ", where, " differ; no changes made there")
return(F)
}
## must change all the method definitions, using the same utility used by
## setMethod when method, generic args differ.
methodDefs = methods@definitions
for(i in seq(along = methodDefs))
el(methodDefs, i) = makeCallForMethod(f, def, el(methodDefs, i))
methods@definitions = methodDefs
if(is(methods@default, "function"))
methods@default = makeCallForMethod(f, def, methods@default)
}
## make the ordinary function object match the generic, if it did before
if(exists(f, where = where)) {
oldF = get(f, where = where)
if(!is(oldF, "function"))
message("ignoring non-function object \"", f, "\" on database ", where)
else if(!identical(oldF, oldDef))
warning("old function for \"", f,
"\" differed from old generic on database ", where,
": function object not changed")
else assign(f, def, where = where)
}
assign(methodsName(f), methods, where = where, meta = 1)
TRUE
}
"makeCallForMethod" =
# constructs a method definition that matches arguments.
# Both `from' and `to' are normally function definitions, the generic and the proposed method.
# A function definition is returned that calls `to' with the arguments
# supplied, plus .Generic and .Signature to provide the methods context. Used by `setMethod'
# and by `replaceGenericDef' to modify methods when the generic function changes args.
#
# Generics and methods are supposed to agree in argument list; when they don't only a
# heuristic attempt can be made to patch the calls. This is the heuristic. It does not
# warn the user of potential problems, but the caller to this function should do so.
function(f, from, to, name = "actualMethod")
{
## generate the function call (using parse to handle the ... case automatically)
args = formalArgs(from)
toArgs = formalArgs(to)
## usually the method has extra arguments (and defaults for some of these)
## and the generic has "..." as a formal argument,
## OR the method's args are a permutation or a subset of the arguments of the generic.
## Here's a test and heuristic for the second case.
if(length(toArgs) <= length(args)) {
which = match(toArgs, args)
if(!any(is.na(which)))
args = args[which]
}
replaceCase = substring(f, nchar(f) - 1) == "<-"
if(replaceCase) {
## construct a replacement expression, to minimize copying
## Nearly always called from setReplaceMethod via setMethod. In this
## case name is "definition", not very meaningful
if(name == "definition") name = paste("local.", f, sep = "")
if(substring(name, nchar(name) - 1) != "<-")
localName = paste(name, "<-", sep = "")
else {
localName = name
name = substring(name, 1, nchar(name) - 2)
}
n = length(args)
call = parse(text = paste("{", name, "(", paste(args[ - n], collapse =
", "), ", .Generic=.Generic, .Signature=.Signature) <- ", el(args, n),
"; ", el(args, 1), "}"))[[1]]
}
else {
localName = name
call = parse(text = paste(name, "(", paste(args, collapse = ", "),
", .Generic=.Generic, .Signature=.Signature)", sep = ""))[[1]]
}
## add .Generic, .Signature arguments to the function. This unfortunately uses knowledge
## of how function definitions are stored, so as to preserve all defaults, etc.
body = functionBody(to)
to = as.list(to)
n = length(to)
to[[n + 2]] = body
valueName = names(to)[[n - 1]]
if(replaceCase) {
which = (n - 1):(n + 1)
to[which] = c("", "", to[n - 1])
names(to)[which] = c(".Generic", ".Signature", valueName)
}
else {
which = n:(n + 1)
to[which] = c("", "")
names(to)[which] = c(".Generic", ".Signature")
}
to = as.function(to)
functionBody(from) = substitute({
X <- TO
CALL
}
, list(X = as.name(localName), CALL = call, TO = to))
from
}