Skip to content

[R-pkg-devel] Using function with same name as argument as default argument

6 messages · Andrew Simmons, Ivan Krylov, Duncan Murdoch +1 more

#
Not sure if this belongs on r-help or r-package-devel; decided for the 
latter as the question is mainly relevant when writing code to be used 
by others.

The issue I run into is that I want to write a function similar to:


foo <- function(x, schema = schema(x)) {
   if (is.null(schema)) stop("schema missing")
   # ...
}

with 'schema()' something like (strongly simplified):

schema <- function(x) attr(x, "schema")


However using both the argument schema and calling the schema() function 
in one of the default arguments is not allowed ans results in the 
following somewhat cryptic error message:

Error in foo(1:3) :
   promise already under evaluation: recursive default argument 
reference or earlier problems?

I am looking for a clean solution to this. I can rename the argument or 
function, but calling it something other than schema feels impractical 
as both refer to the same thing (the schema of x). The best solution I 
have come up with until now is to define a second function to be used in 
default function arguments:

schema_ <- schema

foo <- function(x, schema = schema_(x)) {
   if (is.null(schema)) stop("schema missing")
   # ...
}

I guess another solution would be:

foo <- function(x, schema) {
   if (missing(schema)) schema <- schema(x)
}

But then it is not clear for the user from the interface that the 
'schema' argument can be omitted.

I am hoping some of you have other suggestions.

And, is this something that could be 'fixed' in the R-parser?


Thanks,
Jan
#
Hello,


This isn't something that can be fixed in the parser. If an argument isn't
provided, its default value is evaluated inside the function, so it gives
you a loop where schema = schema(x), but then what's schema, it's
schema(x), thus the recursion error. you could do something like this:

foo <- function (x, schema = schema(x))
{
    if (missing(schema)) {
        rm(schema)
        schema <- schema(x)
    }
}

which, while kinda gross looking, means that schema = schema(x) can be
evaluated without causing a recursion error.
On Mon, Aug 8, 2022, 09:11 Jan van der Laan <rhelp at eoos.dds.nl> wrote:

            

  
  
#
On Mon, 8 Aug 2022 15:10:37 +0200
Jan van der Laan <rhelp at eoos.dds.nl> wrote:

            
Here's a workaround that should work inside a package:

function(x, schema = mypackagename::schema(x))

For inline functions, this workaround is also possible, but I can only
think of an extremely ugly variant:

schema <- function(x) { force(x); 'schema' }
foo <- function(
 x, schema = get('schema', parent.env(environment()))(x)
) { force(schema); 'foo' }
foo(1) # works
#
Hi Andrew and Ivan,

Thanks for the suggestion. Both are good suggestions; better than what I 
have.


Andrews solution:

 > foo <- function (x, schema = schema(x))
 > {
 >      if (missing(schema)) {
 >          rm(schema)
 >          schema <- schema(x)
 >      }
 > }

Ivans:

 > function(x, schema = mypackagename::schema(x))

Both would work in this case. Ivans is a bit cleaner/simpler; Andrews 
appears simpler from a user perspective. Not sure yet which one I will 
use; I am currently leaning towards Ivans solution.

Thanks!
Jan
On 08-08-2022 15:42, Andrew Simmons wrote:
#
I kind of prefer Ivan's solution.  A variation on Andrew's that doesn't 
require you to code the default in two places is:

foo <- function(x, schema = schema(x)) {
   if (missing(schema)) {
     default <- substitute(schema)
     rm(schema)
     schema <- eval(default)
   }
}

You could also change the name of the argument and let users rely on 
partial argument matching:


foo <- function(x, schema. = schema(x)) {
    schema <- schema.   # if you don't want to type the dot again
    if (is.null(schema)) stop("schema missing")
    # ...
}

This allows users to say foo(x, schema = myschema(x)) .

However, I'd say it's not a great design.  Probably changing your 
function name to `getSchema` would be the best overall solution:


foo <- function(x, schema = getSchema(x)) {
    if (is.null(schema)) stop("schema missing")
    # ...
}

Functions do actions, so their names should be verb-like.

Duncan Murdoch
On 08/08/2022 10:23 a.m., Jan van der Laan wrote:
#
Thanks.

I think I prefer Ivan's solution too.
Thought about naming the function something like that. On the other 
hand, a lot of base R functions follow the following pattern in a 
similar case

foo <- levels(x)
levels(x) <- ...

(There is also a `schema<-` method). Other examples of this are 'attr', 
'class', 'length', 'dim', and 'names'. So for getters and setters the 
pattern seems to be to not use get and set. I like this feature of R, so 
would like to stick with it.

Best,
Jan