I agree that failing fast is a good principle. My initial point led the other way though, i.e. any unmatched formal arguments without default values should be handled in one of two ways:
1. Fail the function call. This is what most non-functional languages do e.g. Python >>> def f(x,y,z): x ... >>> f(2) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() takes exactly 3 arguments (1 given) 2. Perform partial application, like some functional languages e.g. Haskell f :: Int -> Int -> Int -> Int f x y z = x *Main> let a = f 2 *Main> :t a a :: Int -> Int -> Int Otherwise if an argument is truly optional, I don't see why a default value cannot be assigned to the formal argument when defining the function (excepting the edge cases you pointed out earlier). Brian On Jul 17, 2013, at 2:35 PM, Peter Meilstrup <[email protected]> wrote: > On Wed, Jul 17, 2013 at 10:20 AM, Ben Bolker <[email protected]> wrote: > >> Brian Rowe <rowe <at> muxspace.com> writes: >> >>> >>> Thanks for the lead. Given the example in ?missing though, >>> wouldn't it be safer to explicitly define a >>> default value of NULL: >>> >>> myplot <- function(x, y=NULL) { >>> if(is.null(y)) { >>> y <- x >>> x <- 1:length(y) >>> } >>> plot(x, y) >>> } >>> >> >> [snip] >> >> In my opinion the missing() functionality can indeed be >> fragile (for example, I don't know how I can manipulate an >> existing call to make an argument be 'missing' when it was >> previously 'non-empty') > > > Like so: > >> thecall <- quote(x[i,j]) >> thecall[[3]] <- quote(expr=) >> thecall > x[, j] > > >> and using an explicit NULL is often >> a good idea. This makes the documentation a tiny bit less >> wieldy if you have lots of parameters ... > > > I could certainly imagine a variant of R in which missing and NULL are > unified, and NULL is the default for any binding that exists but was not > given a value. I would probably prefer it on the grounds of being smaller > and more consistent. (At the C implementation level, R_MissingArg and > R_NilValue are just two parallel uses of the null object pattern with > different behavior, which is a bit silly) > > But one advantage the missing value behavior can have is that it "fails > early", i.e. it generates an error closer to where a function wants to use > a value it was not provided, rather than "failing late," where a NULL > propagates though your data and you have to do more debugging work to find > out where it came from. This kind of fragility can be a good thing as it's > easier to debug problems that happen closer to the point of failure. > > For instance, > >> myplot <- function(y, x=1:length(y)) plot(x,y) >> myplot() > Error in plot(x, y) (from #1) : > error in evaluating the argument 'x' in selecting a method for function > 'plot': Error in length(y) (from #1) : 'y' is missing > > I didn't think about what myplot should do with no arguments. As it turns > out it is an error, as R refuses to pass a missing value along to length() > or plot(), which is reasonable. > > Compare with a default-NULL version. >> myplot <- function(y=NULL, x=1:length(y)) plot(x,y) >> myplot() > > Instead of failing early and generating a stack trace pointing you at the > problem, myplot() now generates a graph with points at (0,0) and (1,1) -- > most surprising! This is because R happily forwards NULL to length() and > plot() where it refused to earlier. In more complicated code nulls can pass > along several layers before causing problems, making those problems more of > a headache to debug. > > Peter > > [[alternative HTML version deleted]] > > ______________________________________________ > [email protected] mailing list > https://stat.ethz.ch/mailman/listinfo/r-devel ______________________________________________ [email protected] mailing list https://stat.ethz.ch/mailman/listinfo/r-devel
