Discussion:
[Roxygen-devel] S4 implementation of @usage
Hadley Wickham
2012-08-29 14:07:41 UTC
Permalink
Hi Vitalie,

It would also be really useful if you could sketch out the S4 design
to work with a tag like usage.

Would you need multiple dispatch so that usage looks like:

setMethod("ProcessTag", signature(tag = "RoccerUsage", object =
"function"), ...)
setMethod("ProcessTag", signature(tag = "RoccerUsage", object =
"classRepresentation"), ...)
setMethod("ProcessTag", signature(tag = "RoccerUsage", object =
"MethodDefinition"), ...)

Is that better than this? (effectively the same as the current s3
implementation)

setMethod("ProccessTag", signature(tag = "RoccerUsage"), function(tag, obj) {
makeUsageObject(obj at value, obj at name)
}
setMethod("makeUsageObject", signature(object = "function"), ...)
setMethod("makeUsageObject", signature(object = "classRepresentation"), ...)
setMethod("makeUsageObject", signature(object = "MethodDefinition"), ...)


Currently the roccer implements the strategy design pattern so that
you can separately specify the parser and the output. How would you do
the same with S4? Multiple inheritance?

setClass("RoccerUsage", contains = c("RoccerParseSingle", "RoccerOutRd"))
setMethod("ProcessTag", signature(tag = "RoccerUsage"), function(tag, roc) ...)
setMethod("OutRd", signature(tag = "RoccerUsage"), function(tag) ...)

or would you do that inside the methods?

setClass("RoccerUsage", contains = "Roccer")
setMethod("Process", signature(tag = "RoccerUsage"), function(tag,
rocblocks) ...)
setMethod("Output", signature(tag = "RoccerUsage"), function(tag,
rocblocks) ...)

?

Hadley
--
Assistant Professor
Department of Statistics / Rice University
http://had.co.nz/
Vitalie Spinu
2012-08-29 17:00:50 UTC
Permalink
Post by Hadley Wickham
Hadley Wickham <hadley at rice.edu>
Hi Vitalie,
It would also be really useful if you could sketch out the S4 design
to work with a tag like usage.
setMethod("ProcessTag", signature(tag = "RoccerUsage", object =
"function"), ...)
setMethod("ProcessTag", signature(tag = "RoccerUsage", object =
"classRepresentation"), ...)
setMethod("ProcessTag", signature(tag = "RoccerUsage", object =
"MethodDefinition"), ...)
Yes, this is precisely what I had in mind. A couple of comments.

1) Its a convention in R that class names are CamelCase (as you did) but
method names are CamelCase which start with small letter (processTag).

It's a very nice convention, especially with
functions_separated_by_underscors conventions which you extensively
use. Code becomes very easy to read, as you know what is method, class
and where is a simple function or object.

2) I think processTag should have 4 arguments (though I might be not
seeing the whole picture). The third argument being RocBlock (or RocDoc?) -- an
object containing all the tags of the current documentation chunk and
an object. The forth being RocPackage containing all the RocDoc objects
collected during the evaluation. The processTag method for a specific
tag need not use the last two. RocBlock is needed, because the tag
processing might depend on other tags in the same documentation chunk.

No need to dispatch on the last 2 arguments as far. May be in the
future when you will have different types of RocBlocks and RocPackage
(oxigen, markdown etc.).

The question is how you make tags "communicate" to each other (global
tags like family or inheritParams). One option is preParse stage (which
I think you are doing right now). Another one, is to add one special
slot to global tags which is a common environment. Then this special
tags can communicate to each other using that environment.
Post by Hadley Wickham
Is that better than this? (effectively the same as the current s3
implementation)
setMethod("ProccessTag", signature(tag = "RoccerUsage"), function(tag, obj) {
makeUsageObject(obj at value, obj at name)
}
setMethod("makeUsageObject", signature(object = "function"), ...)
setMethod("makeUsageObject", signature(object = "classRepresentation"), ...)
setMethod("makeUsageObject", signature(object = "MethodDefinition"), ...)
This is no good, for each Tag you create a method. The main thing about
methods is that you can have one processTag and that works for all tags.
Post by Hadley Wickham
Currently the roccer implements the strategy design pattern so that
you can separately specify the parser and the output. How would you do
the same with S4? Multiple inheritance?
This are methods for each tag. You must have rocParse method and rocRd
method, that's all. Not rocOut, you will pretty soon have rocHTHL,
rocMarkdown etc to output into other formats. (maybe outputRd,
outputHTML etc).
Post by Hadley Wickham
setClass("RoccerUsage", contains = c("RoccerParseSingle",
"RoccerOutRd"))
This is not good. Tag class is an entity to represent an abstract and
isolated notion of a tag concept. It should not have anything to do with
the output output, parsing, printing etc. All of the actions which one
might apply to an object should be implemented as methods. I guess you
are still thinking in C++ way of classes comprising objects structure
and methods in one entity. In S4 they are completely separated.
Post by Hadley Wickham
setMethod("ProcessTag", signature(tag = "RoccerUsage"), function(tag, roc) ...)
setMethod("OutRd", signature(tag = "RoccerUsage"), function(tag) ...)
Ah ... ok, reading it now :) Somehow I missed this on the first skim.
Sure, this is the way!
Post by Hadley Wickham
setClass("RoccerUsage", contains = "Roccer")
setMethod("Process", signature(tag = "RoccerUsage"), function(tag,
rocblocks) ...)
setMethod("Output", signature(tag = "RoccerUsage"), function(tag,
rocblocks) ...)
But wait a second. Why do you need Roccers? What is roccer? You have tag
classes representing the tags.

I will write a rough sketch of the system in a separate mail.

Vitalie
Hadley Wickham
2012-08-29 17:25:19 UTC
Permalink
Post by Vitalie Spinu
Post by Hadley Wickham
Hadley Wickham <hadley at rice.edu>
Hi Vitalie,
It would also be really useful if you could sketch out the S4 design
to work with a tag like usage.
setMethod("ProcessTag", signature(tag = "RoccerUsage", object =
"function"), ...)
setMethod("ProcessTag", signature(tag = "RoccerUsage", object =
"classRepresentation"), ...)
setMethod("ProcessTag", signature(tag = "RoccerUsage", object =
"MethodDefinition"), ...)
Yes, this is precisely what I had in mind. A couple of comments.
1) Its a convention in R that class names are CamelCase (as you did) but
method names are CamelCase which start with small letter (processTag).
Oh, yeah, it'll take my java typing skills a while to come back ;)
Post by Vitalie Spinu
It's a very nice convention, especially with
functions_separated_by_underscors conventions which you extensively
use. Code becomes very easy to read, as you know what is method, class
and where is a simple function or object.
2) I think processTag should have 4 arguments (though I might be not
seeing the whole picture). The third argument being RocBlock (or RocDoc?) -- an
object containing all the tags of the current documentation chunk and
an object. The forth being RocPackage containing all the RocDoc objects
collected during the evaluation. The processTag method for a specific
tag need not use the last two. RocBlock is needed, because the tag
processing might depend on other tags in the same documentation chunk.
So you'd have:

* roccer, a Roccer
* rocblock, a RocBlock
* rocpackage, a RocPackage (i.e. a list of RocBlocks)

and

* object

Object seems a bit awkward because it's part of the rocblock, and is
currently stored in a list with it's value and it's name. But I don't
see how you'd dispatch on it, unless you pull it out into a separate
argument.
Post by Vitalie Spinu
No need to dispatch on the last 2 arguments as far. May be in the
future when you will have different types of RocBlocks and RocPackage
(oxigen, markdown etc.).
Agreed.
Post by Vitalie Spinu
The question is how you make tags "communicate" to each other (global
tags like family or inheritParams). One option is preParse stage (which
I think you are doing right now). Another one, is to add one special
slot to global tags which is a common environment. Then this special
tags can communicate to each other using that environment.
I find the approach of having a pre-parsing phase much easier to test,
which is why I generally prefer it.
Post by Vitalie Spinu
Post by Hadley Wickham
Is that better than this? (effectively the same as the current s3
implementation)
setMethod("ProccessTag", signature(tag = "RoccerUsage"), function(tag, obj) {
makeUsageObject(obj at value, obj at name)
}
setMethod("makeUsageObject", signature(object = "function"), ...)
setMethod("makeUsageObject", signature(object = "classRepresentation"), ...)
setMethod("makeUsageObject", signature(object = "MethodDefinition"), ...)
This is no good, for each Tag you create a method. The main thing about
methods is that you can have one processTag and that works for all tags.
But it would make the arguments to processTag much cleaner.
Post by Vitalie Spinu
Post by Hadley Wickham
Currently the roccer implements the strategy design pattern so that
you can separately specify the parser and the output. How would you do
the same with S4? Multiple inheritance?
This are methods for each tag. You must have rocParse method and rocRd
method, that's all. Not rocOut, you will pretty soon have rocHTHL,
rocMarkdown etc to output into other formats. (maybe outputRd,
outputHTML etc).
Hmmm, so to define a new type of output, you define a new generic like
OutRd, OutNamespace, OutDescription etc. The methods would then
return objects that give information so that the Out object can put
them in the right class. For Rd this is a file name and some rd
commands, for namespace it's a character vector of lines, for
description it's named fields (probably just strings).
Post by Vitalie Spinu
Post by Hadley Wickham
setClass("RoccerUsage", contains = c("RoccerParseSingle",
"RoccerOutRd"))
This is not good. Tag class is an entity to represent an abstract and
isolated notion of a tag concept. It should not have anything to do with
the output output, parsing, printing etc. All of the actions which one
might apply to an object should be implemented as methods. I guess you
are still thinking in C++ way of classes comprising objects structure
and methods in one entity. In S4 they are completely separated.
Post by Hadley Wickham
setMethod("ProcessTag", signature(tag = "RoccerUsage"), function(tag, roc) ...)
setMethod("OutRd", signature(tag = "RoccerUsage"), function(tag) ...)
Ah ... ok, reading it now :) Somehow I missed this on the first skim.
Sure, this is the way!
Post by Hadley Wickham
setClass("RoccerUsage", contains = "Roccer")
setMethod("Process", signature(tag = "RoccerUsage"), function(tag,
rocblocks) ...)
setMethod("Output", signature(tag = "RoccerUsage"), function(tag,
rocblocks) ...)
But wait a second. Why do you need Roccers? What is roccer? You have tag
classes representing the tags.
I'm thinking of a tag as the string "@mytag the tag's content". The
Roccer is the object that converts it into something useful - but I
think you're right that in this model that would be a Tag. The
distinction would be tag strings (parsed) and Tag objects (parsed and
processed).

Hadley
--
Assistant Professor
Department of Statistics / Rice University
http://had.co.nz/
Vitalie Spinu
2012-08-29 18:57:26 UTC
Permalink
I have done my best here https://gist.github.com/3516476

In what follows, I use the same names as in my gist outline, in an
attempt to influence you in the naming matter of course ;).
Post by Hadley Wickham
Hadley Wickham <hadley at rice.edu>
Object seems a bit awkward because it's part of the rocblock, and is
currently stored in a list with it's value and it's name. But I don't
see how you'd dispatch on it, unless you pull it out into a separate
argument.
Yes, dispatch is on the object which is part of rocblock (maybe
roxyBlock?). roxyParse is used only internally, so not a big deal. When
iterating, disassembling and calling roxyParse(rblock at object), rblock at doc)
looks fine to me.
Post by Hadley Wickham
I find the approach of having a pre-parsing phase much easier to test,
which is why I generally prefer it.
Agree, I also find it more transparent. Another way (as I noticed in my
gist) is to allow the roxyParse method to alter the global object
RoxyPackage which contains all the roxyBlocks. Then you might not even
need preParsing stage. (BTW, preParsing should be also a method,
roxyPreParse).
Post by Hadley Wickham
Post by Hadley Wickham
Currently the roccer implements the strategy design pattern so that
you can separately specify the parser and the output. How would you do
the same with S4? Multiple inheritance?
This are methods for each tag. You must have rocParse method and rocRd
method, that's all. Not rocOut, you will pretty soon have rocHTHL,
rocMarkdown etc to output into other formats. (maybe outputRd,
outputHTML etc).
Hmmm, so to define a new type of output, you define a new generic like
OutRd, OutNamespace, OutDescription etc.
I am not really following. outNamespace, OutDescription are global
processing on the whole package. They are not tag, object or roxyDoc
specific.

If so, then there is no need for methods. They are just functions taking
one argument (roxyPackage in my terminology). One only need methods for
tag, doc and object specific tasks.
Post by Hadley Wickham
The methods would then return objects that give information so that
the Out object can put them in the right class. For Rd this is a
file name and some rd commands, for namespace it's a character
vector of lines, for description it's named fields (probably just
strings).
Hm, may be I am misunderstanding something. outRd is a method which
produces the documentation (Rd) file from a roxyDoc object and the
object itself. This is how I understand it.

You seem to talk about some intermediary stage for which I don't really
see the rationale, but may be it's just terminological issue.

Vitalie
Hadley Wickham
2012-08-30 13:57:41 UTC
Permalink
Post by Vitalie Spinu
I have done my best here https://gist.github.com/3516476
Thanks - that's useful. A few comments/questions:

* I don't think the spec for processTag is quite right yet - currently
you have it returning a transformed object of the same class as the
input - but that only works for a limited number of tags. Many tags
also need to modify other tags the rocblock. For example the "@intro"
tag (a virtual tag added to the start of every rocblock) needs to
modify the title, description and details. The @docType tag needs to
modify a lot of other pieces.

It would be possible to rewrite them to "pull" rather than "push",
i.e. so that @title would check @intro, and @keywords would check
@docType but I don't think that's as good a design as it means the
code for one tag needs to be put in multiple places.

* Similarly, I still don't see how tags that operate globally would
work. I think that's partly because the model is so focussed on the
individual tag - the tag classes become very heavy - that's where all
the logic is stored. That's not such a good fit for actions that work
on multiple tags in multiple rocblocks. I wonder if we need a
alternative model for some tags - instead of being stored in each
rocblock, they should be stored globally:

setClass("GlobalTag", contains = "Tag",
slots = "RocBlock" # pointer back to the rocblock that contained them
)
setClass("FamilyTag", contains = "GlobalTag")
setClass("IncludeTag", contains = "GlobalTag")
setClass("InheritParamsTag", contains = "GlobalTag")

# Takes list of tags as input (not sure how dispatch would work) and
rocBlocks as input
# Returns list of rocblocks as output
setGeneric("processGlobalTags")

* I think I need to explain the output model a little bit more - the
output needs to be by tag, and return an intermediate representation
of the output object. More like:

setGeneric("outRd")
setMethod("outRd", "roxyTag", function(tag, rocblock)) {
filename <- getTag(rocblock, "rdname")
rdCommand(tag at name, tag at value, path = filename)
}

This is because the output methods can't write directly to disk - they
may need to do some aggregation:

* multiple rocblocks may output to the same Rd file with @rdname
* all outputs to NAMESPACE need to be sorted and have duplicates removed.

Another possible approach could be:

setClass("OutputRd")
setGeneric("makeOutput", function(input, output) {})
setMethod("makeOutput", c("TagParam", "OutputRd"), ...)
setMethod("makeOutput", c("TagImport", "OutputNamespace"), ...)
setMethod("makeOutput", c("TagIncludes", "OutputDescription") ...)

setMethod("makeOutput", c("RoxyBlock", "Output"), function(input, output) {
lapply(input at tags, makeOutput, output = output)
}
setMethod("makeOutput", c("RoxyPackage", "Output"), function(input, ouput) {
unlist(lapply(input at blocks, makeOutput, output = output), recursive = FALSE)
}

setGeneric("writeOutput", function(output, data) {})
setMethod("writeOutput", c("outputNamespace", "list")) {
lines <- sort(unique(unlist(data)))
write_if_different(lines, "NAMESPACE")
}

That would make it easier for the user to specify which sorts of
output they want because they could just provide a list of Output
objects to roxygenise.
Post by Vitalie Spinu
Post by Hadley Wickham
Object seems a bit awkward because it's part of the rocblock, and is
currently stored in a list with it's value and it's name. But I don't
see how you'd dispatch on it, unless you pull it out into a separate
argument.
Yes, dispatch is on the object which is part of rocblock (maybe
roxyBlock?). roxyParse is used only internally, so not a big deal. When
iterating, disassembling and calling roxyParse(rblock at object), rblock at doc)
looks fine to me.
I think it would still need to be rblock at ***@value, but that's ok too.

Hadley
--
Assistant Professor
Department of Statistics / Rice University
http://had.co.nz/
Hadley Wickham
2012-08-30 15:50:02 UTC
Permalink
Post by Hadley Wickham
* Similarly, I still don't see how tags that operate globally would
work. I think that's partly because the model is so focussed on the
individual tag - the tag classes become very heavy - that's where all
the logic is stored. That's not such a good fit for actions that work
on multiple tags in multiple rocblocks. I wonder if we need a
alternative model for some tags - instead of being stored in each
setClass("GlobalTag", contains = "Tag",
slots = "RocBlock" # pointer back to the rocblock that contained them
)
setClass("FamilyTag", contains = "GlobalTag")
setClass("IncludeTag", contains = "GlobalTag")
setClass("InheritParamsTag", contains = "GlobalTag")
# Takes list of tags as input (not sure how dispatch would work) and
rocBlocks as input
# Returns list of rocblocks as output
setGeneric("processGlobalTags")
Yet another option would make the Blocks (and Tags) into reference
objects so that they could be modified in place (this would also
remove the need for the explicit prerequisite tracking because the
tags could just parse themselves as needed). The big problem is that
it would be much much harder to cache (and that seems really important
for reasonable performance.

Hadley
--
Assistant Professor
Department of Statistics / Rice University
http://had.co.nz/
Vitalie Spinu
2012-08-30 15:57:46 UTC
Permalink
Post by Hadley Wickham
Hadley Wickham <hadley at rice.edu>
I have done my best here https://gist.github.com/3516476
* I don't think the spec for processTag is quite right yet - currently
you have it returning a transformed object of the same class as the
input - but that only works for a limited number of tags. Many tags
tag (a virtual tag added to the start of every rocblock) needs to
modify a lot of other pieces.
How about this. You can have 3 levels on which a tag can perform an
aciton -- a local tag level, on the object documentation (block) level
and on a package level. For each of this actions you have a generic
dispatched on *tag* object:


setGeneric("prepareTag", function(tag) standardGeneric("prepareTag"),
useAsDefault = function(tag) tag)

setGeneric("prepareDoc", function(tag, roxydoc) standardGeneric("prepareDoc"),
useAsDefault = function(tag, roxydoc) roxydoc)

setGeneric("preparePackage", function(tag, roxypackage) standardGeneric("preparePackage"),
useAsDefault = function(tag, roxypackage) roxypackage)

So you have 3 core objects roxyPackage holding a list of roxyBlocks,
roxyBlocks which comprise roxyDoc and object, and finally roxyDoc which
comprise roxyTags.

preparePackage returns roxyPackage
prepareDoc returns roxyDoc
prepareTag returns roxyTag

Pretty simple, isn't it? Only special tags have to declare prepareDoc
and preparePackage.

So roxygenize will iterate 3 times over all tags and call prepareTag on
first iteration, then prepareDoc, and finally preparePackage.

(The above is to give an idea and fix the terminology for the
sequel. The finial implementation will only have one roxyPrepare method
dispatched on both arguments (tag, missing), (Tag, RoxyDoc), (Tag,
roxypackage). Hopefully I am clear enough here.)


Otherwise you need some global exchange. Store roxyBlock globally and
allow the tag prepareTag method to modify it by side effect. Ugly and
not an R-ish way.
Post by Hadley Wickham
setClass("GlobalTag", contains = "Tag",
slots = "RocBlock" # pointer back to the rocblock that contained them
)
pointer? That's not that easy in R, is it? You mean drooping to C and
installing real C pointers to objects?

An alternative could be to make roxyDoc an S4 environment. And each
tag can have a @parent slot holding his parent environment. Same for
roxyPackage object, it can be an environment. And roxyBlock can have a
slot pointing to parent roxyPackage object.

Then prepareTag can have access to his parent roxyDoc and roxyPackage.

As compared to the preparePackage/Doc/Tag approach, the modifications are
done by side effect. Not that nice IMO.
Post by Hadley Wickham
* I think I need to explain the output model a little bit more - the
output needs to be by tag, and return an intermediate representation
setGeneric("outRd")
setMethod("outRd", "roxyTag", function(tag, rocblock)) {
filename <- getTag(rocblock, "rdname")
rdCommand(tag at name, tag at value, path = filename)
}
This is because the output methods can't write directly to disk - they
* all outputs to NAMESPACE need to be sorted and have duplicates removed.
Hm, I was thinking that outRd should return a string containing an Rd
representation of the tag and should have nothing to with the file. It's
a task of a special function (write_rd_file) to aggregate all the tags
from an roxyDoc object and write them into a file.

In other words write_rd_file call outRd on each tag in roxyDoc and
decide how and where to place it.
Post by Hadley Wickham
From what you say, it seems that outRd should also have a global
perspective. This looks like a redundancy to me. The preparation stage
(prepareTag, prepareDoc and preparePackage) should handle all this
global dependencies.

Take for example @rdname. The method procesPackage(rdTag, roxypackage)
should return a new RoxyPackage object, with all RoxyDoc with the same
@rdname unified in one RoxyDoc object. So that by the end of all the
procesXXX stages, the roxyPackage contains only one RoxyDoc object per
output file.
Post by Hadley Wickham
setClass("OutputRd")
setGeneric("makeOutput", function(input, output) {})
setMethod("makeOutput", c("TagParam", "OutputRd"), ...)
setMethod("makeOutput", c("TagImport", "OutputNamespace"), ...)
setMethod("makeOutput", c("TagIncludes", "OutputDescription") ...)
setMethod("makeOutput", c("RoxyBlock", "Output"), function(input, output) {
lapply(input at tags, makeOutput, output = output)
}
setMethod("makeOutput", c("RoxyPackage", "Output"), function(input, ouput) {
unlist(lapply(input at blocks, makeOutput, output = output), recursive = FALSE)
}
setGeneric("writeOutput", function(output, data) {})
setMethod("writeOutput", c("outputNamespace", "list")) {
lines <- sort(unique(unlist(data)))
write_if_different(lines, "NAMESPACE")
}
That would make it easier for the user to specify which sorts of
output they want because they could just provide a list of Output
objects to roxygenise.
Interesting, but it doesn't feel natural to me. It specifies a *type* of
an output by the type of an *input* object which you create specifically
for this purpose (to indicate the type of output). That's tough ;).

I still don't understand why you would need a method to generate a
namespace and description? Isn't this a global action? That is, the
write_namespace function should take as input all objects (roxyPackage),
iterate through all the roxyDoc objects, look into @export field and
finally write a namespace file? Similarly for description.

And why users might want to modify the default namespace generator?

S4 should be used only for those parts of the package which impose
different behavior for different objects. All the rest are simple
functions. It looks to me that you are really over zealous in trying to
use S4 for everything.

A conceptual note. Roxygen is a documentation generator, so the output
is one to one correspondence to the file format (rd, text, html
etc). Making an namespace or description output is unnatural and seems
to be an unnecessary confusion.

I think this trails back to rocklet concept in the first version of the
roxygen. I could never understand what is the point of collate_rocklet
and namespace_rocklet. From the documentation they are objects what
specify an action. This is confusing, as action is usually associated
with a function or a method.

So instead of

roxygenize( ..., roclets = c("collate", "namespace", "rd"))

this would have been much simpler:

roxygenize( ..., collate = TRUE, namespace = TRUE, output = rd)


I am definitely not seeing the full picture here, but I can be pretty
sure that whatever the reason behind those decision was, it could have
been done in a standard R-ish way. There is really no need to confuse
the user with new pseudo class objects like rocklets or roccers or
whatever. Functions, methods, classes and object, that is the standard R
language.

Vitalie
Hadley Wickham
2012-08-30 16:29:07 UTC
Permalink
Post by Vitalie Spinu
How about this. You can have 3 levels on which a tag can perform an
aciton -- a local tag level, on the object documentation (block) level
and on a package level. For each of this actions you have a generic
setGeneric("prepareTag", function(tag) standardGeneric("prepareTag"),
useAsDefault = function(tag) tag)
setGeneric("prepareDoc", function(tag, roxydoc) standardGeneric("prepareDoc"),
useAsDefault = function(tag, roxydoc) roxydoc)
setGeneric("preparePackage", function(tag, roxypackage) standardGeneric("preparePackage"),
useAsDefault = function(tag, roxypackage) roxypackage)
So you have 3 core objects roxyPackage holding a list of roxyBlocks,
roxyBlocks which comprise roxyDoc and object, and finally roxyDoc which
comprise roxyTags.
preparePackage returns roxyPackage
prepareDoc returns roxyDoc
prepareTag returns roxyTag
Pretty simple, isn't it? Only special tags have to declare prepareDoc
and preparePackage.
So roxygenize will iterate 3 times over all tags and call prepareTag on
first iteration, then prepareDoc, and finally preparePackage.
That's better, but generally you only need to call preparePackage
once, not once for each instance of that tag. I was thinking
something like:

setGeneric("roxyProcess", function(input, ...) {
standardGeneric("roxyProcess")
})

setMethod("roxyProcess", "RoxyPackage", function(input) {
# Process each block individually for local tags
input at blocks <- lapply(input at blocks, roxyProcess)

# Process the global tags which
input at blocks <- lapply(input at globalTags, roxyProcess, input at blocks)

input
})

setMethod("roxyProcess", "RoxyBlock", function(input) {
input at tags <- lapply(input at tags, roxyProcess)
input
})
Post by Vitalie Spinu
(The above is to give an idea and fix the terminology for the
sequel. The finial implementation will only have one roxyPrepare method
dispatched on both arguments (tag, missing), (Tag, RoxyDoc), (Tag,
roxypackage). Hopefully I am clear enough here.)
Yup, it makes sense, I just don't think it will work ;)
Post by Vitalie Spinu
Otherwise you need some global exchange. Store roxyBlock globally and
allow the tag prepareTag method to modify it by side effect. Ugly and
not an R-ish way.
Post by Hadley Wickham
setClass("GlobalTag", contains = "Tag",
slots = "RocBlock" # pointer back to the rocblock that contained them
)
pointer? That's not that easy in R, is it? You mean drooping to C and
installing real C pointers to objects?
Well it wouldn't be a dynamic pointer, it would just be copy.
Post by Vitalie Spinu
An alternative could be to make roxyDoc an S4 environment. And each
roxyPackage object, it can be an environment. And roxyBlock can have a
slot pointing to parent roxyPackage object.
Then prepareTag can have access to his parent roxyDoc and roxyPackage.
Yes, then you might as well use reference classes.
Post by Vitalie Spinu
As compared to the preparePackage/Doc/Tag approach, the modifications are
done by side effect. Not that nice IMO.
Agreed.
Post by Vitalie Spinu
Hm, I was thinking that outRd should return a string containing an Rd
representation of the tag and should have nothing to with the file. It's
a task of a special function (write_rd_file) to aggregate all the tags
from an roxyDoc object and write them into a file.
I don't think that can work, because combining Rd commands can't work
at the string level, and it varies from command to command. i.e. if
you have multiple @keywords you do

\keyword{one}
\keyword{two}
\keyword{three}

but for multiple @params you do

\arguments{
\item{one}{desc one}
\item{two}{desc two}
}
Post by Vitalie Spinu
From what you say, it seems that outRd should also have a global
perspective. This looks like a redundancy to me. The preparation stage
(prepareTag, prepareDoc and preparePackage) should handle all this
global dependencies.
should return a new RoxyPackage object, with all RoxyDoc with the same
@rdname unified in one RoxyDoc object. So that by the end of all the
procesXXX stages, the roxyPackage contains only one RoxyDoc object per
output file.
I agree, but it's harder to do that merging at that point than at the
Rd level. (That's from personal experience doing it both ways - I
don't have any evidence to back it up)
Post by Vitalie Spinu
Post by Hadley Wickham
setClass("OutputRd")
setGeneric("makeOutput", function(input, output) {})
setMethod("makeOutput", c("TagParam", "OutputRd"), ...)
setMethod("makeOutput", c("TagImport", "OutputNamespace"), ...)
setMethod("makeOutput", c("TagIncludes", "OutputDescription") ...)
setMethod("makeOutput", c("RoxyBlock", "Output"), function(input, output) {
lapply(input at tags, makeOutput, output = output)
}
setMethod("makeOutput", c("RoxyPackage", "Output"), function(input, ouput) {
unlist(lapply(input at blocks, makeOutput, output = output), recursive = FALSE)
}
setGeneric("writeOutput", function(output, data) {})
setMethod("writeOutput", c("outputNamespace", "list")) {
lines <- sort(unique(unlist(data)))
write_if_different(lines, "NAMESPACE")
}
That would make it easier for the user to specify which sorts of
output they want because they could just provide a list of Output
objects to roxygenise.
Interesting, but it doesn't feel natural to me. It specifies a *type* of
an output by the type of an *input* object which you create specifically
for this purpose (to indicate the type of output). That's tough ;).
If you did it with different methods (outRd, outNamespace,
outDescription), and the user created a new type of output (e.g.
outDemoIndex) how would they tell roxygen to all call the outDemoIndex
on every block/tag?

This style would also be useful for testing - you could have output
objects that don't actually output anything, they just capture all
their inputs for later inspection.
Post by Vitalie Spinu
I still don't understand why you would need a method to generate a
namespace and description? Isn't this a global action? That is, the
write_namespace function should take as input all objects (roxyPackage),
finally write a namespace file? Similarly for description.
Hmmm - I just think it feels more natural for it to be driven by the
individual tags. Each tag knows what it's output should be (these
namespace declarations, these entries in the Rd file), and then the
output object knows how to aggregate them all together and write them
to disk.
Post by Vitalie Spinu
And why users might want to modify the default namespace generator?
They might want to not use it - i.e. if you're managing the namespace
by hand, it'd be nice to tell roxygen not to mess with it.
Post by Vitalie Spinu
S4 should be used only for those parts of the package which impose
different behavior for different objects. All the rest are simple
functions. It looks to me that you are really over zealous in trying to
use S4 for everything.
But output does differ between tags? Some need to output Rd, some
NAMESPACE, etc.
Post by Vitalie Spinu
A conceptual note. Roxygen is a documentation generator, so the output
is one to one correspondence to the file format (rd, text, html
etc). Making an namespace or description output is unnatural and seems
to be an unnecessary confusion.
I don't think that's necessary true. e.g. a potential useful
extension could be to modify @importFrom so that it also lists the
external functions used in the documentation - then it would need to
output to both namespace and Rd. I think there are lots of potential
use cases for output to multiple locations.

Hmmm, but you list rd, html and text as potential output formats - I
definitely don't see html and text as output types. That's something
you might generate from the Rd files, but I don't think it's the job
of roxygen to generate anything that R doesn't use directly. Maybe
that's why we seem to be talking past each other.

I see roxygen being 90% a documentation generator - the other 10% is
automatically generating any other file type that makes package
development easier. This includes the NAMESPACE, some parts of the
DESCRIPTION, the demo index, ...
Post by Vitalie Spinu
I think this trails back to rocklet concept in the first version of the
roxygen. I could never understand what is the point of collate_rocklet
and namespace_rocklet. From the documentation they are objects what
specify an action. This is confusing, as action is usually associated
with a function or a method.
Yes, agreed.
Post by Vitalie Spinu
So instead of
roxygenize( ..., roclets = c("collate", "namespace", "rd"))
roxygenize( ..., collate = TRUE, namespace = TRUE, output = rd)
See I'm thinking

roxygenize( ..., output = c(OutputRd(), OutputCollate(), outputNamespace())

would be even more useful, because it makes it easier to extend
roxygen with new types of output.
Post by Vitalie Spinu
I am definitely not seeing the full picture here, but I can be pretty
sure that whatever the reason behind those decision was, it could have
been done in a standard R-ish way. There is really no need to confuse
the user with new pseudo class objects like rocklets or roccers or
whatever. Functions, methods, classes and object, that is the standard R
language.
Well roclets were standard s3 objects - I think roxygen did work in
standard R way. It's more that the design of roxygen didn't match up
to what extensions were actually needed - but it's really hard to
predict what people will want to extend before you actually write the
system.

Hadley
--
Assistant Professor
Department of Statistics / Rice University
http://had.co.nz/
Vitalie Spinu
2012-08-30 19:12:23 UTC
Permalink
Post by Hadley Wickham
Hadley Wickham <hadley at rice.edu>
So roxygenize will iterate 3 times over all tags and call prepareTag on
first iteration, then prepareDoc, and finally preparePackage.
That's better, but generally you only need to call preparePackage
once, not once for each instance of that tag.
No, no ... that was precisely the main point! It should process each
*tag* at "package level". Then process each *tag* at a "block level" and
finally at "tag level". Here is the complete pseudo code for roxy
package:


roxygenise <- function(...., ## see below for explanation
processors = c(procRd, procNamespace, procCollate, procMyCoolIndex)){
roxypkg <- parse_all_files(...)

roxypkg <- procPackage(roxypkg)

for ( proc in processors )
roxypkg <- proc(roxypkg)

}

### --- package processors ---
setMethod("procPackage", c("RoxyPackage"),
function(package){
...
package <- lapply(package at blocks, processBlock)
## changes by blocks
for ( block in names(package at blocks) )
package <- procPackage(package, block )
})

setMethod("procPackage", c("RoxyPackage", "RoxyBloc"),
function(package, object){
...
## changes by tags
for ( tag in names(object at tags) )
package <- procPackage( package, tag )
})

setMethod("procPackage", c("RoxyPackage", "RoxyTag"),
function(package, object) ... )

### --- block processors ---
setMethod("prodBlock", c("RoxyBloc"),
function(block){
...
block <- lapply(block at tags, procTag)
## package changes by tags
for( tag in input at tags )
block <- procBlock(block, tag)
})

setMethod("prodBlock", c("RoxyBloc", "RoxyTag"),
function(block, object) ... )

### --- tag processors ---
setMethod("prodTag", c("RoxyTag"),
function(tag) ... )

I really looks to me that roxygen will not need more than this.

After the final processing stage (at package level) each element in
"roxypkg" is a self contained entity which can be directly
Rd-ed. Really, after all this stages you should hardly ever need any
further global processing, only local access. And if really needed, you
can proceed recursively as procPackage does above.
Post by Hadley Wickham
Hm, I was thinking that outRd should return a string containing an Rd
representation of the tag and should have nothing to with the file. It's
a task of a special function (write_rd_file) to aggregate all the tags
from an roxyDoc object and write them into a file.
I don't think that can work, because combining Rd commands can't work
at the string level, and it varies from command to command. i.e. if
\keyword{one}
\keyword{two}
\keyword{three}
\arguments{
\item{one}{desc one}
\item{two}{desc two}
}
Good example. Let me fit it into the above paradigm.

After the *block level* processing this RoxyBlock will have a tag of
class "TagArguments" (and *no* tags of class TagParam). That is, it is a
*standalone* entity which can be transformed by outRd into a string.

In other words, TagParam's roxyProcess *block level* method creates a tag
@arguments and removes the corresponding TagParam argument from the
roxyBlock object.

Does this make sense?

Can you give some other difficult examples?
Post by Hadley Wickham
Interesting, but it doesn't feel natural to me. It specifies a *type* of
an output by the type of an *input* object which you create specifically
for this purpose (to indicate the type of output). That's tough ;).
If you did it with different methods (outRd, outNamespace,
outDescription), and the user created a new type of output (e.g.
outDemoIndex) how would they tell roxygen to all call the outDemoIndex
on every block/tag?
Good point. I think your solution of passing processing functions to
roxygenize should work best:

roxygenize( ..., output = c(outRd, outCollate, outNamespace, outDemoIndex))

where outRd, outCollate, outDemoIndex are function *or* generics which
roxygenize will call on the RoxyPackage object. First procPackage
should be called, and then all the "outputers" in turn. All of them
should return back an object of class RoxyPackage which will be passed
to the next processor in the chain.

BTW, why "output"? I find this name confusing. This functions can have
arbitrary side effects, or just change the content of the RoxyPackage
object.

May be processors? procRd, procCollate etc. as in my pseudo code above.
Post by Hadley Wickham
But output does differ between tags? Some need to output Rd, some
NAMESPACE, etc.
BTW, is it a tag task to generate namespace? Isn't it that RoxyDoc's method
should generate it.
Post by Hadley Wickham
A conceptual note. Roxygen is a documentation generator, so the output
is one to one correspondence to the file format (rd, text, html
etc). Making an namespace or description output is unnatural and seems
to be an unnecessary confusion.
I don't think that's necessary true. e.g. a potential useful
external functions used in the documentation - then it would need to
output to both namespace and Rd. I think there are lots of potential
use cases for output to multiple locations.
Ok, I am convinced now. outNamespace (or procNamespace) could also be
a recursive generic.
Post by Hadley Wickham
Hmmm, but you list rd, html and text as potential output formats - I
definitely don't see html and text as output types. That's something
you might generate from the Rd files, but I don't think it's the job
of roxygen to generate anything that R doesn't use directly. Maybe
that's why we seem to be talking past each other.
You are right. I am confused. And my confusion stems from the "output"
terminology which I have hard time to get used to. Somehow it really
doesn't sound right to call a namespace generator, an output. outCollate
is even more weird.

Vitalie.
Hadley Wickham
2012-08-30 20:07:37 UTC
Permalink
Post by Vitalie Spinu
Post by Hadley Wickham
Hadley Wickham <hadley at rice.edu>
So roxygenize will iterate 3 times over all tags and call prepareTag on
first iteration, then prepareDoc, and finally preparePackage.
That's better, but generally you only need to call preparePackage
once, not once for each instance of that tag.
No, no ... that was precisely the main point! It should process each
*tag* at "package level". Then process each *tag* at a "block level" and
finally at "tag level". Here is the complete pseudo code for roxy
Hmmmm, that's an interesting approach.

I had another idea, and that was that what I was calling a GlobalTag
is originally what I was calling a Roccer - i.e. it's something that
takes a RoxyPackage as input and a RoxyPackage as output. Then it
would be a clean separation: RoxyTags act locally (at the RoxyBlock
level) and Roccers act globally (at the RoxyPackage level).

I think it makes sense to make this distinction because the process
you outline below doesn't quite fit the common Roccer (e.g. family,
inheritParams, include) scenario which typically involves two passes:
the first to built up a datastructure that stores information about
the global structure, and the second that goes through and modifies
individual RocBlocks.

I also think it seems wrong to have a generic called procPackage - why
do you have the name of the class it operates on in the generic name?
Post by Vitalie Spinu
roxygenise <- function(...., ## see below for explanation
processors = c(procRd, procNamespace, procCollate, procMyCoolIndex)){
roxypkg <- parse_all_files(...)
roxypkg <- procPackage(roxypkg)
for ( proc in processors )
roxypkg <- proc(roxypkg)
}
I was thinking of something like

roxygenise <- function(..., tags, roccers, outputs) {

pkg <- roxy_init(...) # not an S4 method because it starts the ball rolling
pkg <- roxyProcess(pkg, tags)
pkg <- roxyProcess(pkg, roccers)
roxyWrite(pkg, outputs)
}

where if not specified tags, roccers and outputs would default to
finding all classes that inherited from the appropriate base class.
Post by Vitalie Spinu
Post by Hadley Wickham
I don't think that can work, because combining Rd commands can't work
at the string level, and it varies from command to command. i.e. if
\keyword{one}
\keyword{two}
\keyword{three}
\arguments{
\item{one}{desc one}
\item{two}{desc two}
}
Good example. Let me fit it into the above paradigm.
After the *block level* processing this RoxyBlock will have a tag of
class "TagArguments" (and *no* tags of class TagParam). That is, it is a
*standalone* entity which can be transformed by outRd into a string.
In other words, TagParam's roxyProcess *block level* method creates a tag
@arguments and removes the corresponding TagParam argument from the
roxyBlock object.
Problems:

* calling it "block" level doesn't really make sense because it's now
(potentially) working on multiple blocks

* what happens to the objects? Does each RoxyBlock now have a list of
objects associated with it? If so, how do you associate the usage
statements with the objects. i.e. how would you resolve:

#' Topic a.
#'
#' @rdname shared
a <- function(x) {}

#' Topic b.
#'
#' @rdname shared
#' @usage b() # some comment here
b <- function(x) {}
Post by Vitalie Spinu
Post by Hadley Wickham
If you did it with different methods (outRd, outNamespace,
outDescription), and the user created a new type of output (e.g.
outDemoIndex) how would they tell roxygen to all call the outDemoIndex
on every block/tag?
Good point. I think your solution of passing processing functions to
roxygenize( ..., output = c(outRd, outCollate, outNamespace, outDemoIndex))
where outRd, outCollate, outDemoIndex are function *or* generics which
roxygenize will call on the RoxyPackage object. First procPackage
should be called, and then all the "outputers" in turn. All of them
should return back an object of class RoxyPackage which will be passed
to the next processor in the chain.
BTW, why "output"? I find this name confusing. This functions can have
arbitrary side effects, or just change the content of the RoxyPackage
object.
That's an interesting observation - the output objects have basically
the same semantics as the roccer objects. But I think there are some
important differences:

* Output objects are used solely for their side effects (writing to
disk). They return nothing.

* Roccer objects return a modified RoxyPackage and have no
side-effects. This is important for testing.

* All roccers need to be run before output can occur.
Post by Vitalie Spinu
Post by Hadley Wickham
But output does differ between tags? Some need to output Rd, some
NAMESPACE, etc.
BTW, is it a tag task to generate namespace? Isn't it that RoxyDoc's method
should generate it.
It's the tags task to say where it's output should go - it shouldn't
modify the file on disk, but it should provide the output in suitable
format for writing.
Post by Vitalie Spinu
Post by Hadley Wickham
Hmmm, but you list rd, html and text as potential output formats - I
definitely don't see html and text as output types. That's something
you might generate from the Rd files, but I don't think it's the job
of roxygen to generate anything that R doesn't use directly. Maybe
that's why we seem to be talking past each other.
You are right. I am confused. And my confusion stems from the "output"
terminology which I have hard time to get used to. Somehow it really
doesn't sound right to call a namespace generator, an output. outCollate
is even more weird.
I think of it as an output in the same sense that the RoxyBlocks (or
RoxyPackage) are inputs - everything else is just an intermediate
form. The user only sees what the Output objects do.

Hadley
--
Assistant Professor
Department of Statistics / Rice University
http://had.co.nz/
Vitalie Spinu
2012-08-30 23:30:41 UTC
Permalink
Post by Hadley Wickham
Hadley Wickham <hadley at rice.edu>
No, no ... that was precisely the main point! It should process each
*tag* at "package level". Then process each *tag* at a "block level" and
finally at "tag level". Here is the complete pseudo code for roxy
Hmmmm, that's an interesting approach.
I had another idea, and that was that what I was calling a GlobalTag
is originally what I was calling a Roccer - i.e. it's something that
takes a RoxyPackage as input and a RoxyPackage as output.
You really seem to be emotionally attached to this Roccer. Roccer is an
object which process RoxyPackage, right? So it is a function or method?
That is it is an action (a processor?). After so much time I am still
confused. GlobalTag is an class (it represents an object). And you are
equating roccer and GlobalTag ... ???
Post by Hadley Wickham
Then it would be a clean separation: RoxyTags act locally (at the
RoxyBlock level) and Roccers act globally (at the RoxyPackage
level).
I sort of see your point. It's useful distinction indeed. But the way
you say is really confusing. RoxyTag is a class, it cannot "act" in S4
(or even in Java or C++) worlds. Methods and functions act on objects.

Really, most of my confusion is terminological; you seem to mix objects,
classes, methods and actions in one big duno-what :)
Post by Hadley Wickham
I think it makes sense to make this distinction because the process
you outline below doesn't quite fit the common Roccer (e.g. family,
the first to built up a datastructure that stores information about
the global structure, and the second that goes through and modifies
individual RocBlocks.
Yes, it does. You roccer is my procPackage(package, tag) and
procPackage(package, block) methods. They are free to make as many
stages as they want. But, often, two stages won't even be necessary, as
things can be constructed incrementally.
Post by Hadley Wickham
I also think it seems wrong to have a generic called procPackage - why
do you have the name of the class it operates on in the generic name?
:) because the output is RoxyPackage, and it is not acting only on
RoxyPackage, but also on tags and blocks (and potentially on object
itself!). These are all methods dispatched on *three* arguments:

1) These methods return RoxyPackage:
procPackage(object, package), procPackage(object, package, tag), procPackage(object, package, block)

2) These return RoxyBlock:
procBlock(object, block), procBlock(object, block, tag)

3) This returns RoxyTag
procTag(object, tag)

(I didn't include the 'object'' in my pseudocode for simplicity)


It achieves the functional separation which you are talking about and
offers a finer control. Not only a tag can have a global action on a
package, it can have a sub-global action on a block. Also blocks can
have global actions on a package. Isn't that nice?

In my first attempt I used only one generic roxyParse. And it was
confusing because the input type determines the output type and there
was no clear separation. Just replace procXXX in my previous mail with
roxyProcess:

1) These methods return RoxyPackage:
roxyProcess(object, package), roxyProcess(object, package, tag), roxyProcess(object, package, block)

2) These return RoxyBlock:
roxyProcess(object, block), roxyProcess(object, block, tag)

3) This returns RoxyTag
roxyProcess(object, tag)


I am slightly indifferent between the above two naming conventions. Both
work the same, but with the first is a bit less of a brain stretch.
Post by Hadley Wickham
roxygenise <- function(...., ## see below for explanation
processors = c(procRd, procNamespace, procCollate, procMyCoolIndex)){
roxypkg <- parse_all_files(...)
roxypkg <- procPackage(roxypkg)
for ( proc in processors )
roxypkg <- proc(roxypkg)
}
I was thinking of something like
roxygenise <- function(..., tags, roccers, outputs) {
pkg <- roxy_init(...) # not an S4 method because it starts the ball rolling
pkg <- roxyProcess(pkg, tags)
pkg <- roxyProcess(pkg, roccers)
roxyWrite(pkg, outputs)
}
where if not specified tags, roccers and outputs would default to
finding all classes that inherited from the appropriate base class.
Don't really follow. tags are what? User supplied tags? How is then
roxyProcess is dispatched on tags?

Isn't roxy_init the tag generator? In my world it does precisely
that. It tokenizes the package into small pieces called blocks which
contain tags (roxy_init instantiates all the tags). Then roxyProcess can
be dispatched on those.

You seem to talk about tags, roccers and outputs as functions
(i.e. actions taking on the pkg). But at the same time you dispatch over
those. It's really confusing.

May be the best thing is that you give it a try, and build the first
version of the package according to your vision. Then if I still think
that my approach is more parsimonious I will give a rewrite. Afterward
we can talk in more concrete terms.
Post by Hadley Wickham
Post by Hadley Wickham
I don't think that can work, because combining Rd commands can't work
at the string level, and it varies from command to command. i.e. if
\keyword{one}
\keyword{two}
\keyword{three}
\arguments{
\item{one}{desc one}
\item{two}{desc two}
}
Good example. Let me fit it into the above paradigm.
After the *block level* processing this RoxyBlock will have a tag of
class "TagArguments" (and *no* tags of class TagParam). That is, it is a
*standalone* entity which can be transformed by outRd into a string.
In other words, TagParam's roxyProcess *block level* method creates a tag
@arguments and removes the corresponding TagParam argument from the
roxyBlock object.
* calling it "block" level doesn't really make sense because it's now
(potentially) working on multiple blocks
Not at all. procBlock(block, param) works on a block and generates new
tag of class TagArgument (in the same block!). Then procPackage(package,
rdname) unifies all the argument into one argument inside the final
RoxyBlock object.
Post by Hadley Wickham
* what happens to the objects? Does each RoxyBlock now have a list of
objects associated with it? If so, how do you associate the usage
If you accept my evaluation mechanism (i.e. replacement of
object_from_call), then each RoxyBlock might end up associated with a
list of objects from the very first parsing stage. But this seem not
to be a problem in this case.
Post by Hadley Wickham
#' Topic a.
#'
a <- function(x) {}
#' Topic b.
#'
b <- function(x) {}
The order is procTag -> procBlock -> procPackage

So the first is called procTag(b, usage) which generates from the raw
tag (aka object with @name and @text) a "complete" tag object with all
the other slots filled in. And this tag, being processed, doesn't need
to carry 'b' function further on. And even if it really needs to carry
it, the class "TagUsage" can have a slot "@object" which will hold
object 'b'. And procTag(b, usage) fills in this slot by object 'b'.

Actually, in this paradigm, dispatch on object of procPackage and
procBlock might not be needed at all. Only dispatching procTag on the
object might be enough. Tags which need to carry the object over, can
Post by Hadley Wickham
* Output objects are used solely for their side effects (writing to
disk). They return nothing.
* Roccer objects return a modified RoxyPackage and have no
side-effects. This is important for testing.
* All roccers need to be run before output can occur.
Yes, useful distinction. Especially the last part. For the second one
you cannot really enforce Roccer to have no side effect. The user can do
whatever.

(Really, why not call roccer a processor? And why not call it a function
or method instead of an object? Let's be precise. User's life will be so
much simpler without these new terms.)

Vitalie
Hadley Wickham
2012-08-31 15:57:44 UTC
Permalink
Post by Vitalie Spinu
May be the best thing is that you give it a try, and build the first
version of the package according to your vision. Then if I still think
that my approach is more parsimonious I will give a rewrite. Afterward
we can talk in more concrete terms.
I think that's a really good idea :) Thanks for all the discussion so
far - it's been really helpful. I'll probably send a few smaller
questions as I work through the change to S4 and a more unified object
system.

Hadley
--
Assistant Professor
Department of Statistics / Rice University
http://had.co.nz/
Loading...