By Konrad Rudolph

2019-02-08 16:39:41 8 Comments

I am trying to call the function `function` to define a function in R code.

As we all know™️, `function`is a .Primitive that’s used internally by R to define functions when the user uses the conventional syntax, i.e.

mean1 = function (x, ...) base::mean(x, ...)

But there’s nothing preventing me from calling that primitive directly. Or so I thought. I can call other primitives directly (and even redefine them; for instance, in a moment of madness I overrode R’s builtin `for`). So this is in principle possible.

Yet I cannot get it to work for `function`. Here’s what I tried:

# Works
mean2 = as.function(c(formals(mean), quote(mean(x, ...))))

# Works
mean3 = eval(call('function', formals(mean), quote(mean(x, ...))))

# Error: invalid formal argument list for "function"
mean4 = `function`(formals(mean), quote(mean(x, ...)))

The fact that mean3 in particular works indicates to me that mean4 should work. But it doesn’t. Why?

I checked the definition of the `function` primitive in the R source. do_function is defined in eval.c. And I see that it calls CheckFormals, which ensures that each argument is a symbol, and this fails. But why does it check this, and what does that mean?

And most importantly: Is there a way of calling the `function` primitive directly?

Just to clarify: There are trivial workarounds (this question lists two, and there’s at least a third). But I’d like to understand how this (does not) works.


@Artem Sokolov 2019-02-08 22:01:53

After digging a little bit through the source code, here are a few observations:

  1. The actual function creation is done by mkCLOSXP(). This is what gets called by function() {}, by as.function.default() and by .Primitive("function") (a.k.a. `function`)

  2. as.function.default() gets routed to do_asfunction(), which also calls CheckFormals(). However, it directly constructs these formals a few lines above that.

  3. As you pointed out, the other place where CheckFormals() gets called is inside do_function(). However, I don't think do_function() gets called by anything other than .Primitive("function"), so this is the only situation where CheckFormals() is called on the user's input.

  4. CheckFormals() does actually correctly validate a pairlist object.

You can check the last point yourself by running parts of the CheckFormals() function using inline::cfunction

inline::cfunction( c(x="ANY"),
  'Rprintf("is list?: %d\\nTag1 OK?: %d\\nTag2 OK?: %d\\nTag3 NULL?: %d\\n",
     isList(x), TYPEOF(TAG(x)) == SYMSXP, TYPEOF(TAG(CDR(x))) == SYMSXP,
     CDR(CDR(x)) == R_NilValue); return R_NilValue;' )( formals(mean) )

# is list?: 1
# Tag1 OK?: 1
# Tag2 OK?: 1
# Tag3 NULL?: 1

So, somewhere between you passing formals(means) to .Primitive("function") and it getting forwarded to CheckFormals() by do_function(), the argument loses its validity. (I don't know the R source well enough to tell you how that happens.) However, since do_function() is only called by .Primitive("function"), you don't encounter this situation with any other examples.

@Konrad Rudolph 2019-02-09 10:07:01

You’re spot on as far as your reasoning goes. I think the missing piece to the puzzle is the declaration of function in names.c: Unlike your own C function, do_function doesn’t get called with the evaluated argument; instead, it gets called with unevaluated arguments, so what gets passed isn’t a pairlist, it’s a quoted expression that would evaluate to a pairlist.

@Artem Sokolov 2019-02-09 16:53:55

@KonradRudolph: Do you think this is intended behavior? Neither do_function, nor `function`, nor .Primitive("function") get used internally (as determined by grep-ing the source). So, if a user can't call it directly, who is the function meant for?

@Lionel Henry 2019-02-10 10:24:51

The function .Primitive("function") is called when you evaluate a call to function created by the parser into a function object created by the interpreter. Note that those two kinds of objects print identically, which can be confusing. Check the differences between quote(function(foo, bar) NULL) and the unquoted version. You can also verify with .Internal(inspect(quote(function(foo, bar) NULL))) that the parser correctly creates the formals list in calls to function.

@Konrad Rudolph 2019-02-08 18:12:22

For completeness’ sake, lionel’s answer hints at a way of calling `function` after all. Unfortunately it’s rather restricted, since we cannot pass any argument definition except for NULL:

mean5 = `function`(NULL, mean(x, ...))
formals(mean5) = formals(mean)

(Note the lack of quoting around the body!)

This is of course utterly unpractical (and formals<- internally calls as.function anyway.)

@Lionel Henry 2019-02-08 17:53:27

This is because function is a special primitive:

#> [1] "special"

The arguments are not evaluated, so you have actually passed quote(formals(mean)) instead of the value of formals(mean). I don't think there's a way of calling function directly without evaluation tricks, except with an empty formals list which is just NULL.

@Konrad Rudolph 2019-02-08 17:54:35

I actually tried quoting the formals (as well as using alist), yet as you noticed this also doesn’t work. typeof(`for`) is also “special”, but calling ` for ` manually works.

@Lionel Henry 2019-02-08 17:59:39

Yeah because for takes a symbol and two expressions, for which there is parser syntax. There is no explicit parser syntax for creating a pairlist literal, which is what function() takes.

@Konrad Rudolph 2019-02-08 18:01:18

Indeed. I just figured out that you can call it with a manually generated pairlist — but once again only indirectly because, as you said, there’s no pairlist literal syntax.

Related Questions

Sponsored Content

23 Answered Questions

[SOLVED] How to make a great R reproducible example

  • 2011-05-11 11:12:02
  • Andrie
  • 265414 View
  • 2474 Score
  • 23 Answer
  • Tags:   r r-faq

13 Answered Questions

[SOLVED] How to join (merge) data frames (inner, outer, left, right)

19 Answered Questions

[SOLVED] How to sort a dataframe by multiple column(s)

0 Answered Questions

In R, is it possible to define a function using `function`?

  • 2018-11-09 01:11:08
  • machow
  • 84 View
  • 5 Score
  • 0 Answer
  • Tags:   r

9 Answered Questions

[SOLVED] Grouping functions (tapply, by, aggregate) and the *apply family

1 Answered Questions

0 Answered Questions

r using data table column name variable in function call

  • 2017-11-01 16:46:31
  • Dan82
  • 218 View
  • 0 Score
  • 0 Answer
  • Tags:   r data.table

2 Answered Questions

[SOLVED] lapply formals over named vs unamed functions in a list

  • 2016-07-06 15:42:52
  • Tyler
  • 67 View
  • 1 Score
  • 2 Answer
  • Tags:   r lapply

0 Answered Questions

R: what does the small function do when it is not called from anywhere?

  • 2016-01-15 12:49:29
  • Nikolay Dudaev
  • 735 View
  • 1 Score
  • 0 Answer
  • Tags:   r

1 Answered Questions

[SOLVED] add more argument to summarise in dplyr

  • 2014-09-28 03:00:36
  • MYjx
  • 2485 View
  • 6 Score
  • 1 Answer
  • Tags:   r dplyr

Sponsored Content