This vignette builds on the introduction one to provide a marginally more useful example, and demonstrate a few more features of RcppR6.
The code used here is a demo package called examples
, available
within RcppR6 (system.file("examples/examples",
package="RcppR6")
), and like the introduction
package doesn't
really do anything that you'd really want to do, or need RcppR6 to
do.
``` {r echo=FALSE, results="asis"} set.seed(1) source(system.file("vignette_common.R", package="RcppR6")) path <- vignette_prepare("examples") plain_output(tree(path, "introduction"))
This package defines three classes, of varying complexity. It uses a different way of structuring sources to `introduction`; ``` {r echo=FALSE, results="asis"} yaml_output(readLines(file.path(path, "inst/RcppR6.yml")))
Rather than a single file inst/RcppR6_classes.yml
, there is a
file inst/RcppR6.yml
that lists files to include, relative to the
package root. This isn't necessary, but might help with
organisation. Each file can define one or more classes -- here
they define a single class each.
Similarly, the definitions are spread over three files:
inst/include/examples/uniform.hpp
,
inst/include/examples/stack.hpp
and
inst/include/examples/empty.hpp
.
uniform
This is a similar example to the Rcpp modules example of a uniform distribution object. It's not identical to the modules version. ``` {r echo=FALSE, results="asis"} cpp_output(readLines(file.path(path, "inst/include/examples/uniform.hpp")))
The `Rcpp::RNGScope` bit is probably not needed, actually as Rcpp attributes will sort that out for us. The example is declared with a namespace `examples`; unlike attributes, RcppR6 does not have a problem exporting things that are not in the global namespace. This comes in useful for wrapping library code. In addition to the class definition, there are also some free functions defined; `uniform_get_max` and `uniform_set_max`; these are going to be used to set up active members in the R6 class for getting and setting the `max` field of the class. This pattern (free functions) is useful if you want to do additional error checking on inputs when calling from R than when calling from C++ (e.g., passing an `int` into a function that expects an unsigned integer). Note that the getter takes first argument of type `const Uniform&`; a const reference to a `Uniform` object. A non-const reference would be fine here, as would a *copy* of the object. The setter takes first argument `Uniform&`; this needs to be a reference (and not a pointer). Passing in a copy here will *appear* to work (as in; will compile and run) but will not change the value. The `yaml` for this: ``` {r echo=FALSE, results="asis"} yaml_output(readLines(file.path(path, "inst/uniform.yml")))
In order:
uniform: name_cpp: examples::Uniform
This means that the name of the class on the R side will be
different to the name on the C++ side. We'll export the class as
uniform
(so that uniform(...)
will be the constructor in R) but
that the actuall class we are wrapping is called
examples::Uniform
. This is how RcppR6 deals with namespaces --
just provide the fully qualified name any time you refer to a
class, function or type.
The next line:
forward_declare: true
will arrange to declare (but not define) the class for you. This
means that we can take less care in writing the package include
file (inst/include/examples.h
). In particular, all RcppR6 code
(both examples/RcppR6_pre.hpp
and examples/RcppR6_post.hpp
,
along with Rcpp.h
) can be included before you include
inst/examples/uniform.hpp
because the class will have been
forward declared. That's in contrast with the introduction where
we defined the entire class before including
inst/include/RcppR6_pre.h
so that the as
/wrap
templates would
work correctly (see "Extending Rcpp").
Next, up, the constructor: ``` {r echo=FALSE, results="asis"} yaml <- yaml <- readLines(file.path(path, "inst/uniform.yml")) i_constructor <- grep("\s+constructor:", yaml)[[1]] i_methods <- grep("\s+methods:", yaml)[[1]] i_active <- grep("\s+active:", yaml)[[1]] yaml_output(yaml[i_constructor:(i_methods - 1)])
The first argument here, `roxygen` defines some roxygen content to include in the generated `R/RcppR6.R` file, but without the leading `#'`. This will generate a very minimal set of documentation with the title, parameters (`min` and `max`) and arrange to `@export` the object so it appears in the package `NAMESPACE`. Use of this field is optional, and will generally require yaml's pipe syntax to indicate whitespace should be retained in the multiline string. The `args` field is a yaml ordered map of two arguments. Both are `double`s, and both have default values that will be added to the generated R code: ```r uniform <- function(min=0.0, max=1.0) { ... }
There are two methods draw
and range
:
``` {r echo=FALSE, results="asis"}
yaml_output(yaml[i_methods:(i_active - 1)])
The `draw` method takes a single integer and returns a `Rcpp::NumericVector`. Because no `name_cpp` is given, RcppR6 will assume that there is a method `draw` within the class that can be used. And because no `access` is given RcppR6 assumes that `draw` is a method and not a free function. The `range` method calls the free function `examples::uniform_range()`. The C++ function takes the argument `const Uniform& w` but this argument is *not* referred to in the yaml (the first argument of a free function must take a reference to the object). We have to tell RcppR6 that the function is free (rather than a member) with `access: function` and the name of the function `name_cpp: examples::uniform_range`. There are a bunch of active methods, because they're a bit more varied in the options that they can take ``` {r echo=FALSE, results="asis"} yaml_output(yaml[i_active:length(yaml)])
First, min
and max
are direct field accessors. I've made them
read-only by adding readonly: true
. Without this (by default)
they would be read-write. You can also use name_cpp
here to
access a different named field within the C++ class than the name
of the R field that will be generated.
The field the_min
also accesses the min field, but does so
through the member function get_min
. The name_cpp_set
field
indicates the name of the setter (set_min
). Without providing
this, the field would be read-only.
The field the_max
does the same thing as the_min
, but for the
max
field and uses a pair of free functions
(examples::uniform_get_max
and examples::uniform_set_max
) to
achive this.
The active field u
will return a single random number by calling
the function examples::draw1()
.
Running RcppR6 (this will create other two classes not yet discussed)
RcppR6::install(path)
Run devtools::document
to create the NAMESPACE
file
devtools::document(path)
And load the package:
devtools::load_all(path)
We can create a uniform
object:
u <- uniform() u
Draw 10 random numbers:
u$draw(10)
Or just one:
u$u
The minimum was set to zero and max as one by default:
u$min u$max args(uniform)
These are read-only: ``` {r error=TRUE} u$min <- 100 u$max <- 200
These can be set through the `the_min` and `the_max` fields (which are totally redundant here and included only for demonstration) ``` {r } u$the_min <- 10 u$the_max <- 20
new values set:
u$the_min u$the_max
Random number in new range:
u$u
stack
This example shows how to wrap a class that is defined elsewhere --
std::stack
in this case. It provides an alternative
implementation to the version in the R6 vignette.
``` {r echo=FALSE, results="asis"}
cpp_output(readLines(file.path(path, "inst/include/examples/stack.hpp")))
The comments in the C++ code explain largely what is going on; there are safe wrappers around `pop` and `top` that prevent crashes. Better behavour for `top` on an empty stack might be to throw an error (though that will cause problems as an active member with R6 < 2.0.0.9000, which includes the current version on CRAN - 2.0.0 at the time of writing). Then yaml that goes along with this: ``` {r echo=FALSE, results="asis"} yaml_output(readLines(file.path(path, "inst/stack.yml")))
There's not that much more here than for uniform
:
differs
shows how to wrap an operator (though turning this into
something that dispatches nicely on the R side will take more
works <- stack()
Empty stack has a missing top:
s$top
and throws an error when popped (and does not crash!) ``` {r error=TRUE} s$pop()
Push some numbers on the stack: ``` {r } s$push(1) s$push(10) s$push(100)
Three things on the stack:
s$size
First one is:
s$top
std::stack
does not return on pop
, unlike Python's stack
s$pop() s$top
empty out the stack by popping repeatedly:
while (!s$empty) { s$pop() } s$size
empty
empty
is the simplest posssible RcppR6 class, defined within
the simple.hpp
header file:
``` {r echo=FALSE, results="asis"} cpp_output(readLines(file.path(path, "inst/include/examples/empty.hpp")))
This class defines no methods, no constructors, no fields. It is totally useless. But we can still wrap it up. ``` {r echo=FALSE, results="asis"} yaml_output(readLines(file.path(path, "inst/empty.yml")))
This probably serves no benefit at all.
e <- empty() e
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.