Hi Martin,
thanks a lot again for your suggestions! I played around a bit with it
today and this is the solution that I like the most.
The main extensions compared to your code are:
1) Using Reference Classes (I don't know, but I just like them somehow ;-))
1) Basing method dispatch for plugin methods on multiple signature
arguments to ensure transparency/minimize the risk of name clashes for
plugins
2) Hide as much definition details for signature argument classes from
the user as possible (see 'apiClassesEnsure()' and 'pluginObjectCreate()')
One neat thing would be to get around the warnings when defining plugin
methods ('apiClassesEnsure()' which takes care of setting formal classes
for signature arguments is called at 'run time' when calling
'foo()', so the formal classes are not there yet). But I guess I just
have to turn them off temporarily when sourcing in methods from a directory.
It'd be cool if you could tell me what you think of that approach!
Regards,
Janko
#-------------------------------------------------------------------------------
# APPROACH 6 r-devel
#-------------------------------------------------------------------------------
# Set system environments
.HIVE <- new.env()
.HIVE$.protected <- new.env()
.HIVE$.protected$classes <- new.env()
#+++++
# Define plugin class providing all necessary signature arguments for
method
# dispatch of plugin methods
setRefClass("Plugin",
fields=list(
ns="character", # Namespace
link="character", # Name of the function/method for which the
plugin is intended
mount="character", # 'Mounting point' within the link
function. Possibly the linked function can be open for plugins at
different 'sections'
plugin="character", # Name of the plugin method
src="character" # Main input for plugin method
),
methods=list(
# Processes plugins based on fields signature fields above
pluginProcess=function(...){
pluginProcessRef(.self=.self, ...)
}
)
)
#+++++
# Define a function that takes care of 'registering' the classes needed for
# the signature fields above in order to follow a clean method dispatch
# paradigm based on formal classes
apiClassesEnsure <- function(src, do.overwrite=FALSE,...){
out <- sapply(src, function(x.src){
if(!isClass(x.src)){
x.src <- paste("API_", x.src, sep="")
}
if( !exists(x.src, envir=.HIVE$.protected$classes,
inherits=FALSE) |
do.overwrite
){
cat(paste("apiClassesEnsure/assigning class '", x.src,
"' to '.HIVE$.protected$classes'", sep=""), sep="\n")
if(!isClass(x.src)){
expr <- substitute(
setClass(
Class=CLASS,
contains="NULL",
where=ENVIR
),
list(CLASS=x.src, ENVIR=.HIVE$.protected$classes)
)
eval(expr)
eval(substitute(
assign(CLASS, expr, envir=ENVIR),
list(CLASS=x.src, ENVIR=.HIVE$.protected$classes)
))
} else {
eval(substitute(
assign(CLASS, CLASS, envir=ENVIR),
list(CLASS=x.src, ENVIR=.HIVE$.protected$classes)
))
}
}
out <- x.src
return(out)
})
return(out)
}
#+++++
# Define a function that creates plugin objects
pluginObjectCreate <- function(ns=NULL, link=NULL, mount=NULL, plugin=NULL,
src=NULL, do.overwrite=FALSE){
out <- new("Plugin")
out$initFields(
ns=apiClassesEnsure(src=ns, do.overwrite=do.overwrite),
link=apiClassesEnsure(src=link, do.overwrite=do.overwrite),
mount=apiClassesEnsure(src=mount, do.overwrite=do.overwrite),
plugin=apiClassesEnsure(src=plugin, do.overwrite=do.overwrite),
src=src
)
apiClassesEnsure(src=class(src), do.overwrite=do.overwrite)
return(out)
}
pluginObjectCreate()
pluginObjectCreate()$ns
pluginObjectCreate()$link
pluginObjectCreate()$pluginProcess
#+++++
# Set generics
setGeneric(name="pluginProcessRef", signature=c(".self"),
function(.self, ...) standardGeneric("pluginProcessRef")
)
setGeneric(name="pluginExecute",
signature=c("ns", "link", "mount", "plugin", "src"),
function(ns, link, mount, plugin, src, ...)
standardGeneric("pluginExecute")
)
#+++++
# Set method for 'pluginProcessRef'.
# The method has two modi operandi:
# 1) 'do.explicit.clss = FALSE' implies that plugin methods have been
defined
# based on the 'unprocessed' class names for signature arguments, i.e.
# 'signature(ns="mypkg", link="foo", mount="default", plugin="punct",
# src="character")'
# instead of
# 'signature(ns="API_mypkg", link="API_foo", mount="API_default",
# plugin="API_punct", src="character")'
# 2) 'do.explicit.clss = TRUE' implies the use of the 'processed' class
names
setMethod(
f=pluginProcessRef,
signature=c(.self="Plugin"),
function(.self, do.explicit.clss=FALSE, ...){
out <- NULL
if(length(.self$ns)){
if(!do.explicit.clss){
rgx.subst <- "API_"
ns <- gsub(rgx.subst, "", .self$ns)
names(ns) <- NULL
link <- gsub(rgx.subst, "", .self$link)
names(link) <- NULL
mount <- gsub(rgx.subst, "", .self$mount)
names(mount) <- NULL
plugin <- gsub(rgx.subst, "", .self$plugin)
names(plugin) <- NULL
if(!existsMethod(
f="pluginExecute",
signature=c(ns=ns, link=link, mount=mount,
plugin=plugin,
src=class(.self$src))
)){
stop("Invalid plugin")
}
.pluginExecute <- selectMethod(
"pluginExecute",
signature=c(ns=ns, link=link, mount=mount,
plugin=plugin,
src=class(.self$src)),
useInherited=c(ns=FALSE, link=FALSE, mount=FALSE,
plugin=FALSE,
src=TRUE)
)
out <- .pluginExecute(src=.self$src)
} else {
out <- pluginExecute(ns=new(.self$ns),
link=new(.self$link),
mount=new(.self$mount), plugin=new(.self$plugin),
src=.self$src)
}
}
return(out)
}
)
#+++++
# Define the actual plugin methods. For illustration, one using a implicit
# and the other using explicit class names notation for signature arguments.
# Unfortunately I don't know how to avoid warnings at this point; guess
I can't
setMethod(f=pluginExecute, signature=c(ns="mypkg", link="objectModify",
mount="default", plugin="punct",src="character"),
function(ns, link, mount, plugin, src, ...){
out <- gsub("[[:punct:]]", "", src)
}
)
setMethod(f=pluginExecute, signature=c(ns="API_mypkg",
link="API_objectModify",
mount="API_default", plugin="API_digit", src="character"),
function(ns, link, mount, plugin, src, ...){
out <- gsub("[[:digit:]]", "", src)
}
)
showMethods("pluginExecute")
#+++++
# Define the function/method that should be open for plugins
foo <- function(plugin=pluginObjectCreate(), do.explicit.clss=FALSE, ...){
cat("Here: computations before plugin", sep="\n")
cat(paste("Calling plugin '", class(plugin), "'", sep=""), sep="\n")
out <- plugin$pluginProcess(do.explicit.clss=do.explicit.clss)
cat("Here: computations after plugin", sep="\n")
return(out)
}
#+++++
# Apply
foo()
foo( plugin=pluginObjectCreate(ns="mypkg", link="objectModify",
mount="default",
plugin="punct", src="string___123"))
foo(plugin=pluginObjectCreate(ns="mypkg", link="objectModify",
mount="default",
plugin="digit", src="string123"))
# No such plugin method as explicit class names have been used for 'digit
foo(plugin=pluginObjectCreate(ns="mypkg", link="objectModify",
mount="default",
plugin="digit", src="string123"), do.explicit.clss=TRUE)
# /APPROACH 6 r-devel ----------
On 24.08.2011 06:37, Martin Morgan wrote:
On 08/23/2011 03:02 PM, Janko Thyson wrote:
Dear list,
I was wondering how to best implement some sort of a "plugin" paradigm
using R methods and the dispatcher:
Say we have a function/method ('foo') that does something useful, but
that should be open for extension in ONE specific area by OTHERS using
my package. Of course they could go ahead and write a whole new 'foo'
One possibility is to write class / method pairs. The classes extend
'Plugin', and the methods are on generic 'plug', with the infrastructure
## Approach 1: class / method pairs
setClass("Plugin")
setClass("DefaultPlugin", contains="Plugin")
DefaultPlugin <- function() new("DefaultPlugin")
setGeneric("plug",
function(plugin, src) standardGeneric("plug"),
signature="plugin",
valueClass="character")
setMethod(plug, "Plugin", function(plugin, src) {
src
})
foo <- function(src, plugin=DefaultPlugin()) {
plug(plugin, src)
}
This is extended by writing class / method pairs
setClass("Punct", contains="Plugin")
Punct <- function() new("Punct")
setMethod(plug, "Punct", function(plugin, src) {
gsub("[[:punct:]]", "", src)
})
setClass("Digit", contains="Plugin")
Digit <- function() new("Digit")
setMethod(plug, "Digit", function(plugin, src) {
gsub("[[:digit:]]", "", src)
})
The classes could have slots with state, accessible within the method.
An easier-on-the-user approach might have the Plugin class contain or
have slots of class "function". The user would only be obliged to
provide an appropriate function.
## Approach 2:
setClass("Plugin", prototype=prototype(function(src) {
src
}), contains="function")
Plugin <- function() new("Plugin")
setGeneric("foo",
function(src, plugin) standardGeneric("foo"))
setMethod(foo, c("character", "missing"),
function(src, plugin) foo(src, Plugin()))
setMethod(foo, c("character", "Plugin"),
function(src, plugin) plugin(src))
## 'Developer' classes
setClass("Punct", prototype=prototype(function(src) {
gsub("[[:punct:]]", "", src)
}), contains="Plugin")
Punct <- function() new("Punct")
setClass("Digit", prototype=prototype(function(src) {
gsub("[[:digit:]]", "", src)
}), contains="Plugin")
Digit <- function() new("Digit")
## General-purpose 'user' class
setClass("User", contains="Plugin")
User <- function(fun) new("User", fun)
This could have syntax checking in the validity method to catch some
mistakes early. In the S3 world, this is the approach taken by glm for
its 'family' argument, for instance str(gaussian().
Martin
method including the features they'd like to see, but that's not really
necessary. Rather, they should be able to just write a new "plugin"
method for that part of 'foo' that I'd like to open for such plugins.
The way I chose below works, but generates warnings as my method has
signature arguments that don't correspond to formal classes (which is
totally fine). Of course I could go ahead and make sure that such
"dummy" classes exist, but I was wondering if there's a better way.
It'd be great if anyone could let me know how they handle "plugin"
scenarios based on some sort of method dispatch!
Thanks,
Janko
##### CODE EXAMPLE #####
setGeneric(name="foo", signature=c("src"), function(src, ...)
standardGeneric("foo"))
setGeneric(name="plugin", signature=c("src", "link", "plugin"),
function(src, link, plugin, ...) standardGeneric("plugin")
)
setMethod(f="plugin", signature=signature(src="character", link="foo",
plugin="punct"),
function(src, link, plugin, ...){
out <- gsub("[[:punct:]]", "", src)
return(out)
}
)
setMethod(f="plugin", signature=signature(src="character", link="foo",
plugin="digit"),
function(src, link, plugin, ...){
out <- gsub("[[:digit:]]", "", src)
return(out)
}
)
setMethod(f="foo", signature=signature(src="character"),
function(src, plugin=NULL, ...){
if(!is.null(plugin)){
if(!existsMethod(f="plugin",
signature=c(src=class(src), link="foo", plugin=plugin)
)){
stop("Invalid plugin")
}
.plugin <- selectMethod(
"plugin",
signature=c(src=class(src), link="foo", plugin=plugin),
useInherited=c(src=TRUE, plugin=FALSE)
)
out <- .plugin(src=src)
} else {
out <- paste("Hello world: ", src, sep="")
}
return(out)
}
)
foo(src="Teststring:-1234_56/")
foo(src="Teststring:-1234_56/", plugin="punct")
foo(src="Teststring:-1234_56/", plugin="digit")
______________________________________________
R-devel@r-project.org mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel
______________________________________________
R-devel@r-project.org mailing list
https://stat.ethz.ch/mailman/listinfo/r-devel