diff --git a/NAMESPACE b/NAMESPACE index e1ccf1a..5917db6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,10 +1,5 @@ # Generated by roxygen2 (4.0.1): do not edit by hand -S3method("$",ExternalMethodClass) -S3method("$",ExternalSuperMethods) -S3method("[[",ExternalMethodClass) -S3method("[[",ExternalSuperMethods) S3method(print,R6Class) S3method(print,R6ClassGenerator) -export(createExternalMethodClass) export(createR6Class) diff --git a/R/external_method_class.R b/R/external_method_class.R deleted file mode 100644 index e6d3ad5..0000000 --- a/R/external_method_class.R +++ /dev/null @@ -1,224 +0,0 @@ -#' Create a class with non-reference semantics and externally-stored methods -#' -#' @param classname Name of the class. -#' @param members A list of members, which can be functions and non-functions. -#' @param methods A list of methods for the class. -#' @param inherit A ExternalMethodClass object to inherit from (a superclass). -#' @param lock Should the methods for this class be locked? If locked, it won't -#' be possible to add more methods later. -#' @param parent_env The enclosing environment to use for the methods. If -#' \code{NULL}, keep the methods' existing enclosing environment. -#' @export -#' @examples -#' AnimalHerd <- createExternalMethodClass("AnimalHerd", -#' members = list( -#' animal = "buffalo", -#' count = 0 -#' ), -#' methods = list( -#' initialize = function(self, count = 0) { -#' self$count <- count -#' self -#' }, -#' view = function(self) { -#' paste(rep(self$animal, self$count), collapse = " ") -#' }, -#' reproduce = function(self, mult = 2) { -#' self$count <- self$count * mult -#' invisible(self) -#' } -#' ), -#' lock = FALSE -#' ) -#' -#' herd <- AnimalHerd$new(2) -#' herd$view() -#' # "buffalo buffalo" -#' -#' herd$reproduce() -#' # No change to herd because it doesn't have reference semantics -#' herd$view() -#' # "buffalo buffalo" -#' -#' # Need to save it back into the variable if we want to store the result -#' herd <- herd$reproduce() -#' herd$view() -#' # "buffalo buffalo buffalo buffalo" -#' -#' # Methods that return self are chainable -#' herd$reproduce()$view() -#' # "buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo" -#' -#' # Can add methods after the class has already been created, because we -#' # used lock = FALSE -#' AnimalHerd$methods$grow <- function(self) { -#' self$animal <- toupper(self$animal) -#' self -#' } -#' -#' herd$grow()$view() -#' # "BUFFALO BUFFALO BUFFALO BUFFALO" -#' -#' -#' # Inheritance -#' Person <- createExternalMethodClass("Person", -#' members = list( -#' name = NA, -#' hair = NA -#' ), -#' methods = list( -#' initialize = function(self, name, hair = NA) { -#' self$name <- name -#' self$hair <- hair -#' self$greet() -#' self -#' }, -#' greet = function(self) { -#' cat(paste0("Hello, my name is ", self$name, ".\n")) -#' } -#' ) -#' ) -#' ann <- Person$new("Ann", "black") -#' # Hello, my name is Ann. -#' ann$hair -#' # "black" -#' -#' -#' Lumberjack <- createExternalMethodClass("Lumberjack", -#' inherit = Person, -#' members = list( -#' beard = NA -#' ), -#' methods = list( -#' initialize = function(self, name, hair = NA, beard = NA, super) { -#' self <- super$initialize(name, hair) -#' self$beard <- beard -#' self -#' }, -#' greet = function(self) { -#' cat(paste("I'm a lumberjack and I'm OK.\n")) -#' } -#' ) -#' ) -#' jim <- Lumberjack$new("Jim", "red", "bushy") -#' # I'm a lumberjack and I'm OK. -#' jim$hair -#' # "red" -#' jim$beard -#' # "bushy" -createExternalMethodClass <- function(classname = NULL, members = list(), - methods = NULL, inherit = NULL, - lock = TRUE, parent_env = NULL) { - - if (!all_named(members) || !all_named(methods)) { - stop("All elements of members and methods must be named.") - } - if (length(get_nonfunctions(methods)) != 0) { - stop("Objects in methods must all be functions.") - } - if (any(duplicated(c(names(members), names(methods))))) { - stop("All items in members and methods must have unique names.") - } - if (any(c(names(members), names(methods)) %in% c("self", "super"))) { - stop("Items cannot use reserved names 'self' and 'super'.") - } - - if (!is.null(inherit)) { - if (!inherits(inherit, "ExternalMethodClassGenerator")) { - stop("`inherit` must be a ExternalMethodClassGenerator.") - } - - # Merge the new items over the inherited ones - members <- merge_vectors(inherit$members, members) - methods <- merge_vectors(as.list(inherit$methods), methods) - - # Point to the superclass's methods - methods$super <- inherit$methods - } - - # Enclosing env for methods - methods <- assign_func_envs(methods, parent_env) - # Binding env for methods - methods_env <- new.env(parent = emptyenv(), hash = length(methods) > 100) - # Turn methods into an environment so that it's possible to add methods later - list2env2(methods, envir = methods_env) - if (lock) { - lockEnvironment(methods_env) - } - - classes <- c(classname, get_superclassnames(inherit), "ExternalMethodClass") - - class(members) <- c(classname, "ExternalMethodClass") - attr(members, "methods") <- methods_env - - newfun <- externalMethodsClass_newfun(members) - - structure( - list(new = newfun, classname = classname, members = members, - methods = methods_env, inherit = inherit), - class = "ExternalMethodClassGenerator" - ) -} - -# Return a $new function for the ExternalMethodClassGenerator -externalMethodsClass_newfun <- function(self) { - function(...) { - if (is.function(self$initialize)) { - self <- self$initialize(...) - } else if (length(list(...)) != 0 ) { - stop("Called new() with arguments, but there is no initialize method.") - } - self - } -} - -#' @export -`$.ExternalMethodClass` <- function(x, name) { - if (name %in% names(x)) { - return(.subset2(x, name)) - - } else { - methods <- attr(x, "methods") - fun <- methods[[name]] - - if (is.function(fun)) { - if ("super" %in% names(formals(fun))) { - super <- createExternalSuperMethods(x, methods$super) - return(function(...) fun(self = x, ..., super = super)) - } else { - return(function(...) fun(self = x, ...)) - } - } - NULL - } -} - -#' @export -`[[.ExternalMethodClass` <- `$.ExternalMethodClass` - -# A special object that's created when calling superclass methods -createExternalSuperMethods <- function(obj, methods) { - super <- list(self = obj, methods = methods) - class(super) <- "ExternalSuperMethods" - super -} - -#' @export -`$.ExternalSuperMethods` <- function(x, name) { - self <- .subset2(x, "self") - methods <- .subset2(x, "methods") - fun <- .subset2(methods, name) - - if (is.function(fun)) { - if ("super" %in% names(formals(fun))) { - super <- createExternalSuperMethods(self, methods$super) - return(function(...) fun(self = self, ..., super = super)) - } else { - return(function(...) fun(self = self, ...)) - } - } - NULL -} - -#' @export -`[[.ExternalSuperMethods` <- `$.ExternalSuperMethods` diff --git a/man/createExternalMethodClass.Rd b/man/createExternalMethodClass.Rd deleted file mode 100644 index 1d0fb70..0000000 --- a/man/createExternalMethodClass.Rd +++ /dev/null @@ -1,125 +0,0 @@ -% Generated by roxygen2 (4.0.1): do not edit by hand -\name{createExternalMethodClass} -\alias{createExternalMethodClass} -\title{Create a class with non-reference semantics and externally-stored methods} -\usage{ -createExternalMethodClass(classname = NULL, members = list(), - methods = NULL, inherit = NULL, lock = TRUE, parent_env = NULL) -} -\arguments{ -\item{classname}{Name of the class.} - -\item{members}{A list of members, which can be functions and non-functions.} - -\item{methods}{A list of methods for the class.} - -\item{inherit}{A ExternalMethodClass object to inherit from (a superclass).} - -\item{lock}{Should the methods for this class be locked? If locked, it won't -be possible to add more methods later.} - -\item{parent_env}{The enclosing environment to use for the methods. If -\code{NULL}, keep the methods' existing enclosing environment.} -} -\description{ -Create a class with non-reference semantics and externally-stored methods -} -\examples{ -AnimalHerd <- createExternalMethodClass("AnimalHerd", - members = list( - animal = "buffalo", - count = 0 - ), - methods = list( - initialize = function(self, count = 0) { - self$count <- count - self - }, - view = function(self) { - paste(rep(self$animal, self$count), collapse = " ") - }, - reproduce = function(self, mult = 2) { - self$count <- self$count * mult - invisible(self) - } - ), - lock = FALSE -) - -herd <- AnimalHerd$new(2) -herd$view() -# "buffalo buffalo" - -herd$reproduce() -# No change to herd because it doesn't have reference semantics -herd$view() -# "buffalo buffalo" - -# Need to save it back into the variable if we want to store the result -herd <- herd$reproduce() -herd$view() -# "buffalo buffalo buffalo buffalo" - -# Methods that return self are chainable -herd$reproduce()$view() -# "buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo" - -# Can add methods after the class has already been created, because we -# used lock = FALSE -AnimalHerd$methods$grow <- function(self) { - self$animal <- toupper(self$animal) - self -} - -herd$grow()$view() -# "BUFFALO BUFFALO BUFFALO BUFFALO" - - -# Inheritance -Person <- createExternalMethodClass("Person", - members = list( - name = NA, - hair = NA - ), - methods = list( - initialize = function(self, name, hair = NA) { - self$name <- name - self$hair <- hair - self$greet() - self - }, - greet = function(self) { - cat(paste0("Hello, my name is ", self$name, ".\\n")) - } - ) -) -ann <- Person$new("Ann", "black") -# Hello, my name is Ann. -ann$hair -# "black" - - -Lumberjack <- createExternalMethodClass("Lumberjack", - inherit = Person, - members = list( - beard = NA - ), - methods = list( - initialize = function(self, name, hair = NA, beard = NA, super) { - self <- super$initialize(name, hair) - self$beard <- beard - self - }, - greet = function(self) { - cat(paste("I'm a lumberjack and I'm OK.\\n")) - } - ) -) -jim <- Lumberjack$new("Jim", "red", "bushy") -# I'm a lumberjack and I'm OK. -jim$hair -# "red" -jim$beard -# "bushy" -} - diff --git a/tests/testthat/test-extmethodclass.R b/tests/testthat/test-extmethodclass.R deleted file mode 100644 index 3306aac..0000000 --- a/tests/testthat/test-extmethodclass.R +++ /dev/null @@ -1,229 +0,0 @@ -context("ext-methodclass") - -test_that("initialization", { - AC <- createExternalMethodClass("AC", - members = list(x = 1), - methods = list( - initialize = function(self, x) { - self <- self$setx2(2) # Call another class method - self - }, - setx2 = function(self, x) { - self$x <- x * 2 - self - } - ) - ) - A <- AC$new(2) - expect_identical(A$x, 4) - - # No initialize method: throw error if arguments are passed in - AC <- createExternalMethodClass("AC", members = list(x = 1)) - expect_error(AC$new(3)) -}) - -test_that("empty members and methods are allowed", { - # No initialize method: throw error if arguments are passed in - AC <- createExternalMethodClass("AC") - expect_that(AC$new(), not(throws_error())) -}) - -test_that("object contains members but not methods", { - AC <- createExternalMethodClass("AC", - members = list(x = 1), - methods = list( - getx = function(self, x) { - self$x - } - ) - ) - A <- AC$new() - expect_identical(A$x, 1) - expect_false("getx" %in% names(A)) -}) - - -test_that("object is not a reference object", { - AC <- createExternalMethodClass("AC", - members = list(x = 1), - methods = list( - setx = function(self, x) { - self$x <- x - self - } - ) - ) - A <- AC$new() - expect_identical(A$setx(2)$x, 2) - expect_identical(A$x, 1) -}) - - -test_that("Validity checks on creation", { - # All arguments must be named - expect_error(createExternalMethodClass("AC", members = list(1))) - expect_error(createExternalMethodClass("AC", methods = list(1))) - - # Names can't be duplicated - expect_error(createExternalMethodClass("AC", members = list(a=1, a=2))) - expect_error(createExternalMethodClass("AC", members = list(a=1), - methods = list(a = function() 1))) - - # Reserved names - expect_error(createExternalMethodClass("AC", members = list(self = 1))) - expect_error(createExternalMethodClass("AC", members = list(super = 1))) -}) - - -test_that("methods require 'self' to find each other", { - # It would be nice within a method to be able to use gety() instead of - # self$gety(), but it's not possible because we to automatically pass - # `self` to the call, and this is handled by $.ExternalMethodClass. - gety <- function() 0 - AC <- createExternalMethodClass("AC", - members = list(x = 1, y = 10), - methods = list( - getx = function(self) self$x, - gety = function(self) self$y, - sumxy = function(self) { - self$getx() + gety() # Shouldn't find self$gety - } - ) - ) - A <- AC$new() - expect_identical(A$sumxy(), 1) - - # The method runs in this environment - expect_identical(environment(attr(A, "methods")$sumxy), environment()) -}) - - -test_that("inheritance", { - AC <- createExternalMethodClass("AC", - members = list(x = 0, x2 = 1, y = 1), - methods = list( - initialize = function(self, x = 1) { - self <- self$setx(x) # This function is not overridden in Bc - self$x2 <- self$getx() # This function is overridden in BC - self - }, - setx = function(self, x) { self$x <- x; self }, - getx = function(self) self$x - ) - ) - BC <- createExternalMethodClass("BC", - inherit = AC, - members = list(x = 2, y = 2), - methods = list( - getx = function(self) self$x + 20 - ) - ) - B <- BC$new() - expect_identical(B$x, 1) - expect_identical(B$x2, 21) # self$getx() was overridden, called from initialize - expect_identical(B$y, 2) # Overrides AC's initial value of y - expect_identical(B$getx(), 21) # getx() was overridden -}) - - -test_that("inheritance with $super ", { - AC <- createExternalMethodClass("AC", - members = list(x = 0, y = 0), - methods = list( - initialize = function(self, x = 1) { - self$x <- x - self$y <- self$newy() - self - }, - getx = function(self) self$x, - newy = function(self) 1 - ) - ) - BC <- createExternalMethodClass("BC", - inherit = AC, - methods = list( - initialize = function(self, x = 2, super) { - super$initialize(x + 20) - }, - - getx = function(self, x, super) super$getx() + 20, - newy = function(self) 20 - ) - ) - B <- BC$new() - expect_identical(B$x, 22) - expect_identical(B$y, 20) - expect_identical(B$getx(), 42) - - CC <- createExternalMethodClass("CC", - inherit = BC, - methods = list( - initialize = function(self, x = 3, super) super$initialize(x + 300), - getx = function(self, x, super) super$getx() + 300, - newy = function(self) 300 - ) - ) - C <- CC$new() - expect_identical(C$x, 323) - expect_identical(C$y, 300) - expect_identical(C$getx(), 643) -}) - - -test_that("adding methods after class has been created", { - # Can't add if locked - AC <- createExternalMethodClass("AC", - members = list(x = 0), - methods = list( - getx = function(self) self$x, - newy = function(self) 1 - ), - lock = TRUE - ) - BC <- createExternalMethodClass("BC", - inherit = AC, - methods = list( - getx = function(self, x, super) super$getx() + 20 - ), - lock = TRUE - ) - A <- AC$new() - B <- BC$new() - expect_error(AC$methods$foo <- function(self) 2) - expect_error(BC$methods$foo <- function(self) 2) - expect_error(BC$methods$super$foo <- function(self) 2) - - # Can add if not locked - AC <- createExternalMethodClass("AC", - members = list(x = 0), - methods = list( - getx = function(self) self$x, - newy = function(self) 1 - ), - lock = FALSE - ) - BC <- createExternalMethodClass("BC", - inherit = AC, - methods = list( - getx = function(self, x, super) super$getx() + 20 - ), - lock = FALSE - ) - A <- AC$new() - B <- BC$new() - AC$methods$foo <- function(self) 2 - BC$methods$foo <- function(self) 3 - BC$methods$superfoo <- function(self, super) super$foo() - expect_identical(A$foo(), 2) - expect_identical(B$foo(), 3) - expect_identical(B$superfoo(), 2) - - # Modifying superclass methods - BC$methods$super$foo <- function(self) 4 - expect_identical(B$superfoo(), 4) - - # It would be nice if this worked, but it would require a reference from the - # superclass to the subclass, which probably isn't worth the trouble. - # BC$methods$super$bar <- function(self) 5 - # expect_identical(B$bar(), 5) -})