File: 030.setMethodS3.R

package info (click to toggle)
r-cran-r.methodss3 1.8.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 300 kB
  • sloc: sh: 12; makefile: 2
file content (318 lines) | stat: -rw-r--r-- 13,829 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
###########################################################################/**
# @RdocDefault setMethodS3
#
# @title "Creates an S3 method"
#
# \description{
#  Creates an S3 method. A function with name \code{<name>.<class>} will
#  be set to \code{definition}. The method will get the modifiers specified
#  by \code{modifiers}.  If there exists no generic function for this method,
#  it will be created automatically.
# }
#
# @synopsis
#
# \arguments{
#   \item{name}{The name of the method.}
#   \item{class}{The class for which the method should be defined. If
#      \code{class == "default"} a function with name \code{<name>.default}
#      will be created.}
#   \item{definition}{The method definition.}
#   \item{private, protected}{If \code{private=TRUE}, the method is declared
#      private. If \code{protected=TRUE}, the method is declared protected.
#      In all other cases the method is declared public.}
#   \item{export}{A @logical setting attribute \code{"export"}.}
#   \item{static}{If @TRUE this method is defined to be static,
#      otherwise not. Currently this has no effect expect as an indicator.}
#   \item{abstract}{If @TRUE this method is defined to be abstract,
#      otherwise not. Currently this has no effect expect as an indicator.}
#   \item{trial}{If @TRUE this method is defined to be a trial method,
#      otherwise not. A trial method is a method that is introduced to be
#      tried out and it might be modified, replaced or even removed in a
#      future release. Some people prefer to call trial versions, beta
#      version. Currently this has no effect expect as an indicator.}
#   \item{deprecated}{If @TRUE this method is defined to be deprecated,
#      otherwise not. Currently this has no effect expect as an indicator.}
#   \item{envir}{The environment for where this method should be stored.}
#   \item{overwrite}{If @TRUE an already existing generic function and an
#      already existing method with the same name (and of the same class)
#      will be overwritten, otherwise not.}
#   \item{conflict}{If a method already exists with the same name (and of
#      the same class), different actions can be taken. If \code{"error"},
#      an exception will be thrown and the method will not be created.
#      If \code{"warning"}, a @warning will be given and the method \emph{will}
#      be created, otherwise the conflict will be passed unnoticed.}
#   \item{createGeneric, exportGeneric}{If \code{createGeneric=TRUE},
#      a generic S3/UseMethod function is defined for this method,
#      iff missing, and \code{exportGeneric} species attribute
#      \code{"export"} of it.}
#   \item{appendVarArgs}{If @TRUE, argument \code{...} is added with a
#      warning, if missing.  For special methods such as \code{$} and
#      \code{[[}, this is never done (argument is ignored).
#      This will increase the chances that the method is consistent with a
#      generic function with many arguments and/or argument \code{...}.}
#   \item{validators}{An optional @list of @functions that can be used
#      to assert that the generated method meets certain criteria.}
#   \item{...}{Passed to @see "setGenericS3", iff called.}
# }
#
# @examples "../incl/setMethodS3.Rex"
#
# \seealso{
#   For more information about S3, see @see "base::UseMethod".
# }
#
# @author
#
# @keyword "programming"
# @keyword "methods"
#*/###########################################################################
setMethodS3.default <- function(name, class="default", definition, private=FALSE, protected=FALSE, export=FALSE, static=FALSE, abstract=FALSE, trial=FALSE, deprecated=FALSE, envir=parent.frame(), overwrite=TRUE, conflict=c("warning", "error", "quiet"), createGeneric=TRUE, exportGeneric=TRUE, appendVarArgs=TRUE, validators=getOption("R.methodsS3:validators:setMethodS3"), ...) {
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # Validate arguments
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # Argument 'name':
  if (nchar(name) == 0L) {
    stop("Cannot set S3 method. Argument 'name' is empty.")
  }

  # Argument 'class':
  if (nchar(class) == 0L) {
    stop("Cannot set S3 method. Argument 'class' is empty.")
  }

  # Argument 'conflict':
  conflict <- match.arg(conflict)


  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # Backward compatibility tests
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  args <- list(...)
  if (is.element("enforceRCC", names(args))) {
    .Defunct(msg = "Argument 'enforceRCC' of setMethodS3() has been replaced by argument 'validators'.")
  }


  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 1. Test the definition using validators
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if (!is.null(validators)) {
    for (validator in validators) {
      validator(name=name, class=class, definition=definition, private=private, protected=protected, static=static, abstract=abstract, trial=trial, deprecated=deprecated, envir=envir, overwrite=overwrite, conflict=conflict, createGeneric=createGeneric, appendVarArgs=appendVarArgs, type="setMethodS3")
    }
  }

  # Ignore argument 'appendVarArgs' if a "special" method
  # or a replacement method.
  if (appendVarArgs) {
    # (a) Do not append '...' for the following methods
    ignores <- c("$", "$<-", "[[", "[[<-", "[", "[<-")
    ignores <- c(ignores, "==")
    ignores <- c(ignores, "+", "-", "*", "/", "^", "%%", "%/%")
    appendVarArgs <- !is.element(name, ignores)

    if (appendVarArgs) {
      # (b) Neither functions with any of these name patterns
      ignorePatterns <- c("<-$", "^%[^%]*%$")
      ignores <- (sapply(ignorePatterns, FUN=regexpr, name) != -1L)
      appendVarArgs <- appendVarArgs && !any(ignores)
    }
  }

  # Check for forbidden names.
  if (is.element(name, R.KEYWORDS))
    stop("Method names must not be same as a reserved keyword in R: ", name)

  if (class == "ANY") class <- "default"

  # Create the modifiers
  if (private)
    protection <- "private"
  else if (protected)
    protection <- "protected"
  else
    protection <- "public"

  modifiers <- protection
  if (static == TRUE) modifiers <- c(modifiers, "static")
  if (abstract == TRUE) modifiers <- c(modifiers, "abstract")
  if (deprecated == TRUE) modifiers <- c(modifiers, "deprecated")
  if (trial == TRUE) modifiers <- c(modifiers, "trial")

  if (missing(definition) && abstract == TRUE) {
    # Set default 'definition'.
    src <- paste("...R.oo.definition <- function(...) stop(\"Method \\\"", name, "\\\" is defined abstract in class \\\"", class, "\\\" and has not been overridden by any of the subclasses: \", class(list(...)[[1]])[1])", sep="")
    expr <- parse(text=src)

    # If just defining a local 'definition' function, to be used below,
    # one will get warnings "using .GlobalEnv instead of package:<pkg>"
    # when loading the package *with lazy loading*. I do not understand
    # the reasons for it, but here follows a trick in order to not get
    # such warnings. It kinda borrows the 'envir' frame to define a local
    # function. It works, but don't ask me why. /HB 2005-02-25
    eval(expr, envir=envir)
    definition <- get("...R.oo.definition", envir=envir)
    rm(list="...R.oo.definition", envir=envir)
  }


  # Create the class method 'name':
  methodName <- paste(name, class, sep=".")

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 2. Find the environment where sys.source() loads the package, which is
  # the local variable (argument) of sys.source() named as "envir".
  # Unfortunately, the only way we can be sure which of the parent frames
  # are the sys.source() function frame is to compare its definition with
  # each of the definitions of the parent frames using sys.function().
  # Comment: sys.source() is used by library() and require() for loading
  # packages. Also note that packages that are currently loaded are not in
  # the search path, cf. search(), and there and standard exists() will not
  # find it. *Not* checking the currently loading environment would *not*
  # be harmful, but it would produce too many warnings.
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  sys.source.def <- get("sys.source", mode="function", envir=baseenv())
  loadenv <- NULL
  for (framePos in sys.parents()[-1L]) {
    if (identical(sys.source.def, sys.function(framePos))) {
      loadenv <- parent.frame(framePos)
      break
    }
  }


  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 3. Check for preexisting functions with the same name
  #     i) in the environment that we are saving to ('envir'),
  #    ii) in the currently loading environment ('loadenv'), or
  #   iii) (optional) in the environments in the search path.
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  envirs <- c(envir, loadenv)
  if (getOption("R.methodsS3:useSearchPath", TRUE)) {
    envirs <- c(envirs, lapply(search(), FUN=as.environment))
  }
  inherits <- rep(FALSE, times=length(envirs))
  checkImports <- getOption("R.methodsS3:checkImports:setGenericS3", FALSE)
  if (checkImports) inherits[1:2] <- TRUE

  fcn <- .findFunction(methodName, envir=envirs, inherits=inherits)
  fcnDef <- fcn$fcn; fcnPkg <- fcn$pkg


  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 4. Append '...' if missing.
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if (appendVarArgs) {
    if (!hasVarArgs(definition)) {
      warning("Added missing argument '...' to make it more compatible with a generic function: ", methodName)
#      definition <- appendVarArgs(definition)

      # As above, to avoid "using .GlobalEnv instead of package:<pkg>"
      # warnings, we do the below trick. /HB 2005-02-25
      assign("...R.oo.definition", definition, envir=envir)
      eval(substitute(fcn <- R.methodsS3::appendVarArgs(fcn), list(fcn=as.name("...R.oo.definition"))), envir=envir)
      definition <- get("...R.oo.definition", envir=envir)
      rm(list="...R.oo.definition", envir=envir)
    }
  }

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 5. Validate replacement functions (since R CMD check will complain)
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if (regexpr("<-$", name) != -1L) {
    f <- formals(definition)

    fStr <- capture.output(args(definition))[[1]]
    fStr <- sub("^[\t\n\f\r ]*", "", fStr) # trim() is not available
    fStr <- sub("[\t\n\f\r ]*$", "", fStr) # when package loads!

    if (names(f)[length(f)] != "value") {
      ## covr: skip=2
      stop("Last argument of a ", name,
                              "() method should be named 'value': ", fStr)
    }
  }


  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 5b. Validate arguments for 'picky' methods.
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  pickyMethods <- list(
    "$"    = c(NA_character_, "name"),
    "$<-"  = c(NA_character_, "name", "value")
  )

  if (is.element(name, names(pickyMethods))) {
    f <- formals(definition)

    fStr <- capture.output(args(definition))[[1L]]
    fStr <- sub("^[\t\n\f\r ]*", "", fStr) # trim() is not available
    fStr <- sub("[\t\n\f\r ]*$", "", fStr) # when package loads!

    reqArgs <- pickyMethods[[name]]
    nbrOfReqArgs <- length(reqArgs)

    # Check for correct number of arguments
    if (length(f) != nbrOfReqArgs) {
      ## covr: skip=2
      stop("There should be exactly ", nbrOfReqArgs, " arguments of a ",
                                              name, "() method: ", fStr)
    }

    for (kk in 1:nbrOfReqArgs) {
      if (!is.na(reqArgs[kk]) && (names(f)[kk] != reqArgs[kk])) {
        ## covr: skip=2
        stop("Argument #", kk, " in a ", name,
             "() method, should be named '", reqArgs[kk], "': ", fStr)
      }
    }
  }


  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 6. Assign/create the new method
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if (is.null(fcnDef) || overwrite) {
    # Create
    expr <- substitute({
        fcn <- definition
        `R.methodsS3_export<-` <- get("export<-", mode="function",
                        envir=asNamespace("R.methodsS3"), inherits=FALSE)
        R.methodsS3_export(fcn) <- doExport
        rm(list="R.methodsS3_export<-")
        attr(fcn, "S3class") <- class
        attr(fcn, "modifiers") <- modifiers
      }, list(fcn=as.name(methodName), class=class, definition=definition,
              doExport=export, modifiers=modifiers)
    )
    # Assign
    eval(expr, envir=envir)
  }

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 7. Report that a method was redefined?
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if (!is.null(fcnDef)) {
    msg <- paste("Method already existed and was",
                  if (overwrite != TRUE) " not", " overwritten: ", sep="")
    if (is.null(conflict))
      conflict <- "quiet"
    if (conflict == "quiet") {
    } else if (conflict == "warning") {
      warning(msg, methodName)
    } else
      stop(msg, methodName)
  }

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # 8. Create a generic function?
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  if (createGeneric) {
    setGenericS3(name, export=exportGeneric, envir=envir, validators=validators, ...)
  }
} # setMethodS3.default()
S3class(setMethodS3.default) <- "default"
export(setMethodS3.default) <- FALSE

setGenericS3("setMethodS3")