[R] Sending "..." to a C external

Douglas Bates bates at stat.wisc.edu
Tue Aug 26 00:33:55 CEST 2008


On Mon, Aug 25, 2008 at 12:28 PM, John Tillinghast <tilling at gmail.com> wrote:
>
>
> On Sat, Aug 23, 2008 at 11:46 AM, Douglas Bates <bates at stat.wisc.edu> wrote:
>
> I don't think it is necessary to go through the agonies of dealing
> with the argument list in a .External call., which is what I assume
> you are doing. (You haven't given us very much information on how you
> are trying to pass the ... argument and such information would be very
> helpful.  The readers of this list are quite intelligent but none, as
> far as I know, have claimed to be telepathic.)
> ---

> This is the first project for which I've tried to write C code for R.
> I am so innocent that I don't even know what I have to explain to get help.

> This is to speed up a friend's R package (BB), which is for general-purpose
> continuous optimization. So it has to do many of the same things as nlm or
> optim: take a function provided by the user, which might have any number of
> arguments, and optimize over one (vector) argument.
> As with optim, this C code will pass all the arguments for the user-provided
> R function back into R, over and over.
> I am open to any suggestions about how to do this simply and efficiently.

In this sort of programming, "simply" is rarely achievable.

Looking at the code in the BB package on CRAN it seems that the
current version is written entirely in R and the calls to the
objective function are created by code like

fargs <- list(...)

Fnew <- do.call("fn", append(list(xnew), fargs))

One way to accomplish this in C code is to create an environment in
which to evaluate the function and then to create a function call to
be evaluated in that environment.  Creating a function call is not
always that easy, however.  It is sometimes easier to pass an
expression or a formula as the "fn" argument because you can then
strip out the function call without needing to construct it.  Consider

> str(expression(foo(x, y = 30))[[1]])
 language foo(x, y = 30)
> str((~ foo(x, y = 30))[[2]])
 language foo(x, y = 30)

Once you have the function call (which is what a language object is),
you decide which argument has not been specified or, more simply, you
require that the first argument be some name like 'x' and that this is
the argument that will be varied by the optimizer.

One way to do this with formulas is

formeval <- function(par, form)
{
    stopifnot(inherits(form, "formula"), length(form) == 2)
    fc <- form[[2]]                     # the function call
    env <- new.env(parent = environment(form)) # its environment
    stopifnot(is.name(fc[[2]]),
              as.character(fc[[2]]) == "x")
    assign("x", par, env = env)
    eval(fc, env)
}

Then

> foo <- function(x, y = 40) x + y
> formeval(1:3, ~ foo(x))
[1] 41 42 43
> formeval(1:3, ~ foo(x, y = 3))
[1] 4 5 6
> y <- 6
> formeval(1:3, ~ foo(x, y))
[1] 7 8 9

Once you have checked that the function call and the environment are
as you want you can pass them through the .Call interface to a C
function.  In the C function you use FindVarInFrame to determine the
SEXP named "x" in the environment then check that it is numeric and
keep rewriting the value of the REAL pointer and forcing an evaluation
of the function call in that environment.

As I said, "simply" is not usually achievable.  Further discussion
should probably move to the R-devel list.

>> On Fri, Aug 22, 2008 at 2:16 PM, John Tillinghast <tilling at gmail.com>
>> wrote:
>> > I'm trying to figure this out with "Writing R Extensions" but there's
>> > not a
>> > lot of detail on this issue.
>> > I want to write a (very simple really) C external that will be able to
>> > take
>> > "..." as an argument.
>> > (It's for optimizing a function that may have several parameters besides
>> > the
>> > ones being optimized.)
>>
>> > I got the "showArgs" code (from R-exts) to compile and install, but it
>> > only
>> > gives me error messages when I run it. I think I'm supposed to pass it
>> > different arguments from what I'm doing, but I have no idea which ones.
>>
>> > What exactly are CAR, CDR, and CADR anyway? Why did the R development
>> > team
>> > choose this very un-C-like set of commands? They are not explained much
>> > in
>> > R-exts.
>>
>> Emmanuel has answered that - very engagingly too.  On the train from
>> Dortmund to Dusseldorf last week I was describing exactly that
>> etymology of the names CAR, CDR, CDDR, ... to my companions but I got
>> it wrong.  I had remembered CDR as "contents of the data register" but
>> Emmanuel is correct that it was "contents of the decrement register".
>>
>>
>>
>> The way that I would go about this is using .Call in something like
>>
>> .Call("myCfunction", arg1, arg2, dots = list(...), PACKAGE = "myPackage")
>>
>> Then in your C code you check the length and the names of the dots
>> argument and take appropriate action.
>>
>> An alternative, if you want to use the ... arguments in an R
>> expression to be evaluated by your optimizer, is to create an
>> environment, assign the elements of list(...) to the appropriate names
>> in that environment and pass the environment through .Call to be used
>> as the evaluation environment for your R expression.  There is a
>> somewhat complicated example of this in the nlmer function in the lme4
>> package which you can find at http://lme4.r-forge.r-project.org/.
>> However, I don't feel embarrassed about the example being complicated.
>>  This is complex stuff and it is not surprising that it isn't
>> completely straightforward to accomplish. If you feel that this is
>> opaque in R i can hardly wait to see what you think about writing the
>> SPSS version.
>
>



More information about the R-help mailing list