Skip to content

Commit

Permalink
added basic interface support
Browse files Browse the repository at this point in the history
  • Loading branch information
Janko Thyson committed Feb 16, 2016
1 parent f87fe29 commit f61cb17
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 6 deletions.
12 changes: 10 additions & 2 deletions R/generator_funs.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ generator_funs$get_inherit <- function() {
eval(inherit, parent_env, baseenv())
}

# This function returns the implemented interface superclass object
generator_funs$get_implement <- function() {
# The baseenv() arg speeds up eval a tiny bit
eval(implement, parent_env, baseenv())
}

# This is the $has_private function for a R6ClassGenerator. This copy of it
# won't run properly; it needs to be copied, and its parent environment set to
# the generator object environment.
Expand All @@ -13,10 +19,12 @@ generator_funs$has_private <- function() {
inherit <- get_inherit()
if (!is.null(private_fields) || !is.null(private_methods))
TRUE
else if (is.null(inherit))
else if (is.null(inherit) && is.null(implement))
FALSE
else
else if (!is.null(inherit))
inherit$has_private()
else
implement$has_private()
}

# This is the $set function for a R6ClassGenerator. This copy of it won't run
Expand Down
9 changes: 9 additions & 0 deletions R/interface_utils.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
getImplementClassname <- function(x) {
# gen <- try(getAnywhere(class(x)[1]), silent = TRUE)
gen <- try(get(class(x)[1]), silent = TRUE)
if (!inherits(gen, "try-error") &&
!is.null(implement <- gen$get_implement())
) {
implement$classname
}
}
71 changes: 68 additions & 3 deletions R/new.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
generator_funs$new <- function(...) {
# Get superclass object -------------------------------------------
inherit <- get_inherit()
implement <- get_implement()

# Some checks on superclass ---------------------------------------
if (!is.null(inherit)) {
Expand All @@ -19,21 +20,22 @@ generator_funs$new <- function(...) {
merge_vectors(recursive_merge(obj$get_inherit(), which), obj[[which]])
}
public_fields <- merge_vectors(recursive_merge(inherit, "public_fields"),
public_fields)
public_fields)
private_fields <- merge_vectors(recursive_merge(inherit, "private_fields"),
private_fields)
}

if (class) {
classes <- c(classname, get_superclassnames(inherit), "R6")
# classes <- c(classname, get_superclassnames(inherit), "R6")
classes <- unique(c(classname, get_superclassnames(inherit),
get_superclassnames(implement), "R6"))
} else {
classes <- NULL
}

# Precompute some things ------------------------------------------
has_priv <- has_private()


# Create binding and enclosing environments -----------------------
if (portable) {
# When portable==TRUE, the public binding environment is separate from the
Expand Down Expand Up @@ -105,6 +107,69 @@ generator_funs$new <- function(...) {
active <- merge_vectors(super_struct$active, active)
}

# Implemented interface AFTER concrete superclass has been processed as it
# "only" defines abstract methods that can be overloaded by this very class
# its concrete superclass.
# As the actual "crucial" superclass aspects (public and private fields and
# methods) have already been processed, this shouldn't break any of the
# current implementation aspects.
if (!is.null(implement)) {
if (portable) {
# Set up the superclass objects
super_struct <- create_super_env(implement, public_bind_env,
private_bind_env, portable = TRUE)
} else {
# Set up the superclass objects
super_struct <- create_super_env(implement, public_bind_env, portable = FALSE)
}

# Cache decoupled state of env or interface superclass for sanity checks
# (see below)
implement_super_struct <- as.list(super_struct)

enclos_env$super <- super_struct$bind_env

# Merge this level's methods over the superclass methods
public_methods <- merge_vectors(super_struct$public_methods, public_methods)
private_methods <- merge_vectors(super_struct$private_methods, private_methods)
active <- merge_vectors(super_struct$active, active)

# Note:
# If running the same code for `implement` as for `inherit` interferes with
# current behavior of `super_struct` and associated aspects the following
# would also be an alternative.
# As we only need a check of the public methods here in order to mimick the
# concept/behavior of interfaces, the following should suffice:
# implement_super_struct <- implement

# Sanity checks
# Throw an error in case neither the superclass nor the actual class
# overloads/implements any of the abstract public methods defined by the
# interface superclass as I think this would be a violation of the concept
# of a class IMPLEMENTING an interface from an OOD perspective.
# Note:
# The gsub part is only necessary due to the fact that the `clone` method
# features this suffix --> a possible minor inconsistency with regard to
# remaining methods such as `has_private` etc.?

public_methods_interface <- implement_super_struct$public_methods
interface_funs <- setdiff(names(public_methods_interface),
gsub("_method$", "", names(generator_funs)))
if (length(interface_funs)) {
err_list <- unlist(sapply(interface_funs, function(fun) {
if (identical(
body(public_methods_interface[[fun]]),
body(public_methods[[fun]])
)) {
sprintf("Non-implemented interface method: %s", fun)
}
}))
if (length(err_list)) {
stop(paste(c("\n", err_list), collapse = "\n"))
}
}
}

# Copy objects to public bind environment -------------------------
list2env2(public_methods, envir = public_bind_env)
list2env2(public_fields, envir = public_bind_env)
Expand Down
16 changes: 16 additions & 0 deletions R/print.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ format.R6 <- function(x, ...) {

# If there's another class besides first class and R6
classes <- setdiff(class(x), "R6")
if (!is.null(name <- getImplementClassname(x))) {
# --> doesn't work for all scenarios as `get(class(x)[1])` sometimes
# returns an error
ret <- c(ret, paste0(" Implements interface: <", name, ">"))
# Update classes for "actual inherits part"
classes <- classes[classes != name]
} else if (any(idx <- grepl("^I", classes))) {
ret <- c(ret, paste0(" Implements interface: <", classes[idx], ">"))
# Update classes for "actual inherits part"
classes <- classes[!idx]
}

if (length(classes) >= 2) {
ret <- c(ret, paste0(" Inherits from: <", classes[2], ">"))
}
Expand Down Expand Up @@ -46,6 +58,10 @@ format.R6ClassGenerator <- function(x, ...) {
ret <- c(ret, paste0(" Inherits from: <", deparse(x$inherit), ">"))
}

if (!is.null(x$implement)) {
ret <- c(ret, paste0(" Implements interface: <", deparse(x$implement), ">"))
}

ret <- c(ret,
" Public:",
indent(object_summaries(x$public_fields), 4),
Expand Down
15 changes: 14 additions & 1 deletion R/r6_class.R
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@
#' @param cloneable If \code{TRUE} (the default), the generated objects will
#' have method named \code{$clone}, which makes a copy of the object.
#' @param lock Deprecated as of version 2.1; use \code{lock_class} instead.
#' @param implement A R6ClassGenerator object to inherit from which represents
#' an interface class; this is very similar to argument \code{inherit} with
#' the subtle difference that such objects merely mimick abstract classes that
#' only define abstract methods and must not contain \strong{any} data fields.
#' The main benefit is being able to write code that fits the \emph{SOLID principles
#' of object-oriented design}
#' (\url{https://en.wikipedia.org/wiki/SOLID_%28object-oriented_design%29}).
#' @examples
#' # A queue ---------------------------------------------------------
#' Queue <- R6Class("Queue",
Expand Down Expand Up @@ -470,7 +477,8 @@ R6Class <- encapsulate(function(classname = NULL, public = list(),
inherit = NULL, lock_objects = TRUE,
class = TRUE, portable = TRUE,
lock_class = FALSE, cloneable = TRUE,
parent_env = parent.frame(), lock) {
parent_env = parent.frame(), lock,
implement = NULL) {

if (!all_named(public) || !all_named(private) || !all_named(active))
stop("All elements of public, private, and active must be named.")
Expand Down Expand Up @@ -531,6 +539,11 @@ R6Class <- encapsulate(function(classname = NULL, public = list(),
# the parent_env, it should return the superclass object.
generator$inherit <- substitute(inherit)

# Capture the unevaluated expression for the interface implementation
# superclass; when evaluated in the parent_env, it should return the
# superclass object.
generator$implement <- substitute(implement)

# Names of methods for which to enable debugging
generator$debug_names <- character(0)

Expand Down
107 changes: 107 additions & 0 deletions tests/testthat/test-interfaces.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
context("interfaces")

test_that("Test generator object structure", {
IFoo <- R6Class("IFoo",
public = list(foo = function() stop("I'm the inferace method"))
)
Foo <- R6Class("Foo", implement = IFoo,
public = list(foo = function(n = 1) private$x[1:n]),
private = list(x = letters)
)
expect_true(exists("implement", Foo))
expect_true(identical(Foo$implement, as.name("IFoo")))
expect_true(exists("get_implement", Foo))
expect_true(identical(Foo$get_implement(), IFoo))
})

test_that("Interface implemented correctly", {
IFoo <- R6Class("IFoo",
public = list(foo = function() stop("I'm the inferace method"))
)
Foo <- R6Class("Foo", implement = IFoo,
public = list(foo = function(n = 1) private$x[1:n]),
private = list(x = letters)
)
expect_is(inst <- Foo$new(), "Foo")
expect_true(inherits(inst, "IFoo"))
})

test_that("Interface implemented incorrectly", {
IFoo <- R6Class("IFoo",
public = list(foo = function() stop("I'm the inferace method"))
)
Foo <- R6Class("Foo", implement = IFoo,
private = list(x = letters)
)
expect_error(Foo$new(), "Non-implemented interface method: foo")
})

test_that("Interface and standard inheritance", {
IFoo <- R6Class("IFoo",
public = list(foo = function() stop("I'm the inferace method"))
)
BaseClass <- R6Class("BaseClass",
public = list(foo = function(n = 1) private$x[1:n])
)
Foo <- R6Class("Foo", implement = IFoo, inherit = BaseClass,
private = list(x = letters)
)
expect_is(inst <- Foo$new(), "Foo")
expect_true(inherits(inst, "BaseClass"))
expect_true(inherits(inst, "IFoo"))

expect_identical(inst$foo(3), letters[1:3])
})

context("interfaces: print method")

test_that("Test print method: standard inheritance", {
skip("Manual only. Print helper `getImplementClassname` still off with
respect to enclosing frames")
IFoo <- R6Class("IFoo",
public = list(foo = function() stop("I'm the inferace method"))
)
Foo <- R6Class("Foo", inherit = IFoo,
public = list(foo = function(n = 1) private$x[1:n]),
private = list(x = letters)
)
expect_true(any(grepl("Inherits from: <IFoo>", capture.output(Foo))))

inst <- Foo$new()
print(capture.output(inst))
expect_true(any(grepl("Inherits from: <IFoo>", capture.output(inst))))
})

test_that("Test print method: interface implementation", {
# skip("Manual only. Print helper `getImplementClassname` still off with
# respect to enclosing frames")
IFoo <- R6Class("IFoo",
public = list(foo = function() stop("I'm the inferace method"))
)
Foo <- R6Class("Foo", implement = IFoo,
public = list(foo = function(n = 1) private$x[1:n]),
private = list(x = letters)
)
expect_true(any(grepl("Implements interface: <IFoo>", capture.output(Foo))))

inst <- Foo$new()
expect_true(any(grepl("Implements interface: <IFoo>", capture.output(inst))))
})

test_that("Test print method: interface and standard inheritance", {
IFoo <- R6Class("IFoo",
public = list(foo = function() stop("I'm the inferace method"))
)
BaseClass <- R6Class("BaseClass",
public = list(foo = function(n = 1) private$x[1:n])
)
Foo <- R6Class("Foo", implement = IFoo, inherit = BaseClass,
private = list(x = letters)
)
expect_true(any(grepl("Inherits from: <BaseClass>", capture.output(Foo))))
expect_true(any(grepl("Implements interface: <IFoo>", capture.output(Foo))))

inst <- Foo$new()
expect_true(any(grepl("Inherits from: <BaseClass>", capture.output(inst))))
expect_true(any(grepl("Implements interface: <IFoo>", capture.output(inst))))
})

0 comments on commit f61cb17

Please sign in to comment.