4

I have a problem with ellipsis when I use optional arguments in my function definition. To clarify, I define following functions:

func1 <- function (x) (x-2)^2

func3 <- function (fun, arg.curve.user){
  arg.curve.user$expr <-  substitute(func1)
  arg.curve.default <- list(col = "blue", n = 1000, main = "This is a test")
  arg.curve <- modifyList (arg.curve.default, arg.curve.user)
  do.call("curve", arg.curve)
}

# optimizes func1 and call func2 to plot func1
func2 <- function (lb, ub, n.restarts = 5, n.sim = 10, ...){
  arg.curve.user <- as.list(substitute(list(...)))
  output <- gosolnp(fun = func1, LB = lb, UB = ub,  n.restarts =  n.restarts, 
  n.sim =  n.sim)$par
  func3(fun = func1, arg.curve.user = arg.curve.user)
   return(output)
}

By calling func2, func1 is optimized and also plotted through a func3 call (package Rsolnp is required).

func2 ( lb = 0, ub = 8, n.restarts = 5, n.sim = 10, n = 200, from = 0, to = 8)

But suppose a user misspells n.restarts and writes nrestarts:

func2 ( lb = 0, ub = 8, nrestarts = 5, n.sim = 10, n = 200, from = 0, to = 8)

In this case, I expects R to implement the following plans to deal with absence of n.restarts:

  1. assigns default value, i.e. 5, to n.restarts as an optional argument
  2. declares a warning at the end: "nrestarts" is not a graphical parameter

But this does not happen and R assigns value of n (200) to n.restarts instead!!

Can anyone help me to fix this problem?

Many thanks

Gavin Simpson
  • 170,508
  • 25
  • 396
  • 453
Ehsan Masoudi
  • 712
  • 10
  • 19
  • 1
    This happens because you have multiple arguments starting with `n`. Thus, when R evaluates the arguments, it matches to the first argument that matches the pattern. Try rewriting your function with argument names that are more different, e.g. use `restarts=5` instead of `n.restarts=5`. – Andrie Jan 15 '13 at 09:51
  • 1
    See also http://cran.r-project.org/doc/manuals/R-lang.html#Argument-matching – user1935457 Jan 15 '13 at 09:54

2 Answers2

8

It is partial matching the n argument to n.restarts when one is not supplied by the user. Instead, and contrary to the advice of @Andrie (which will work, of course), there is a mechanism that allows you to continue in the manner you have with an argument n and and argument n.restarts. The trick is to place arguments you want to match exactly after the ....

func2 <- function (lb, ub, ..., n.restarts = 5, n.sim = 10){
  writeLines(paste("Value of `n.restarts` is", n.restarts))
  arg.curve.user <- as.list(substitute(list(...)))
  output <- gosolnp(fun = func1, LB = lb, UB = ub,  n.restarts =  n.restarts, 
                    n.sim =  n.sim)$par
  func3(fun = func1, arg.curve.user = arg.curve.user)
  output
}

In use this gives:

> func2 (lb = 0, ub = 8, n.restarts = 2, n.sim = 10, n = 200,
+        from = 0, to = 8)
Value of `n.restarts` is 2          <---- Here!

Iter: 1 fn: 6.926e-15    Pars:  2.00000
Iter: 2 fn: 2.501e-15    Pars:  2.00000
solnp--> Completed in 2 iterations

Iter: 1 fn: 8.336e-16    Pars:  2.00000
Iter: 2 fn: 8.336e-16    Pars:  2.00000
solnp--> Completed in 2 iterations
[1] 2
> func2 (lb = 0, ub = 8, nrestarts = 2, n.sim = 10, n = 200,
+        from = 0, to = 8)
Value of `n.restarts` is 5          <---- Here! Default

Iter: 1 fn: 2.83e-15     Pars:  2.00000
Iter: 2 fn: 2.5e-15  Pars:  2.00000
solnp--> Completed in 2 iterations

Iter: 1 fn: 2.037e-15    Pars:  2.00000
Iter: 2 fn: 2.037e-15    Pars:  2.00000
solnp--> Completed in 2 iterations

Iter: 1 fn: 1.087e-15    Pars:  2.00000
Iter: 2 fn: 1.087e-15    Pars:  2.00000
solnp--> Completed in 2 iterations

Iter: 1 fn: 8.558e-16    Pars:  2.00000
Iter: 2 fn: 8.558e-16    Pars:  2.00000
solnp--> Completed in 2 iterations

Iter: 1 fn: 7.147e-16    Pars:  2.00000
Iter: 2 fn: 7.147e-16    Pars:  2.00000
solnp--> Completed in 2 iterations
[1] 2
Warning messages:
1: In plot.window(...) : "nrestarts" is not a graphical parameter
2: In plot.xy(xy, type, ...) : "nrestarts" is not a graphical parameter
3: In axis(side = side, at = at, labels = labels, ...) :
  "nrestarts" is not a graphical parameter
4: In axis(side = side, at = at, labels = labels, ...) :
  "nrestarts" is not a graphical parameter
5: In box(...) : "nrestarts" is not a graphical parameter
6: In title(...) : "nrestarts" is not a graphical parameter
Gavin Simpson
  • 170,508
  • 25
  • 396
  • 453
  • If the formal arguments contain ‘...’ then partial matching is only applied to arguments that precede it. [link](http://cran.r-project.org/doc/manuals/R-lang.html#Argument-matching) – Ehsan Masoudi Jan 15 '13 at 10:46
  • @Gavin Simpson I would like to publish a package in CRAN. Is this nice trick professional or even legal for this purpose. Because I did not see any package that their export functions had such this arguments structure or at least argument ... always is explained at the end of arguments list in html help page. – Ehsan Masoudi Jan 15 '13 at 11:11
  • It is perfectly valid; it is documented behaviour in the R Lang manual and it is used extensively within R itself. The ordering of explanation in the Arguments section can be arbitrary; it may make sense to document `...` last as it is just a catch all for extra arguments to other functions. – Gavin Simpson Jan 15 '13 at 11:18
2

If you stick with regular argument evaluation you are more likely to get warnings off the bat. Again, you don't need to use special evaluation to get the behaviour that you want. Using non-standard evaluation is a bad idea as you are unlikely to exactly reproduce R's default behaviour, causing subtle bugs for you and your users.

library(Rsolnp)

func1 <- function (x) (x-2)^2

func3 <- function (fun, col = "blue", n = 1000, main = "This is a test", ...){
  curve(func1, ..., n = n, col = col, main = main)
}

# optimizes func1 and call func2 to plot func1
func2 <- function (lb, ub, n.restarts = 5, n.sim = 10, ...){
  output <- gosolnp(fun = func1, LB = lb, UB = ub, n.restarts = n.restarts, 
  n.sim =  n.sim)$par
  func3(fun = func1, ...)
  return(output)
}

Then when you run:

func2 ( lb = 0, ub = 8, nrestarts = 5, n.sim = 10, n = 200, from = 0, to = 8)

you get warnings along the lines of

Warning messages:
1: In plot.window(...) : "nrestarts" is not a graphical parameter
2: In plot.xy(xy, type, ...) : "nrestarts" is not a graphical parameter
3: In axis(side = side, at = at, labels = labels, ...) :
  "nrestarts" is not a graphical parameter
4: In axis(side = side, at = at, labels = labels, ...) :
  "nrestarts" is not a graphical parameter
5: In box(...) : "nrestarts" is not a graphical parameter
6: In title(...) : "nrestarts" is not a graphical parameter
hadley
  • 102,019
  • 32
  • 183
  • 245
  • by this method value of n (200) is assigned to n.restarts and a kind of silent ignore happens for argument n (in case of n.restarts misspelling). – Ehsan Masoudi Jan 15 '13 at 14:55
  • 1
    You can fix that using exactly the same method as in @GavinSimpson's answer. But if you really want to eliminate that sort of error, don't use `...` – hadley Jan 15 '13 at 19:00