"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 }