Re: R-beta: S Compatibility (again)

Bill Venables (wvenable@attunga.stats.adelaide.edu.au)
Tue, 14 Apr 1998 07:12:05 +0930


Date: Tue, 14 Apr 1998 07:12:05 +0930
Message-Id: <9804132142.AA23705@attunga.stats.adelaide.edu.au>
From: Bill Venables <wvenable@attunga.stats.adelaide.edu.au>
To: Luke Tierney <luke@stat.umn.edu>
Subject: Re: R-beta: S Compatibility (again)
In-Reply-To: <9804131554.AA12497@nokomis.stat.umn.edu>
 <9804131554.AA12497@nokomis.stat.umn.edu>

Luke Tierney writes:
 > The need to make heavy use of substitute or eval is, in my
 > view, not a strengh of a language.  The right tool for this
 > job, in my view at least, is lexical scoping and closures. R
 > provides these directly, so a much simpler definition is
 > 
 > pdf.order<-
 > function (n, r, pfun, dfun) 
 > {
 >   con <- round(exp(lgamma(n + 1) - lgamma(r) - lgamma(n - r + 1)))
 >   function(x) {
 >     Fx <- pfun(x)
 >     con * Fx^(r - 1) * (1 - Fx)^(n - r) * dfun(x)
 >   }
 > }
 > 
 > > pdf.order(9, 5, pnorm, dnorm)(0)
 > [1] 0.981772

Aha!  Very neat indeed.  This is one of those examples that make
concepts like lexical scoping and closure much clearer, (at least
to me).

 > The free variables con, n, r, pfun and dfun in the returned
 > function refer to the variables in the defining environment
 > (this is lexical scope).

Yes.  I notice the result is a function "with environment".  It
would be interesting to see how these objects are structured and
how big they were, but I see R draws a discrete veil over this by
declaring functions "not subsetable".  I presume, though, that
all objects in the environment must be copied as part of the
created object, and not just the names essentially as pointers as
the S substitute() solution effectively does.

 > 
 > Since the free variables in the value function are not
 > modified you can do this in S if you abstact out the closure
 > creation operation into a function MC,
 > 
 > pdf.order<-
 > function (n, r, pfun, dfun) 
 > {
 >   con <- round(exp(lgamma(n + 1) - lgamma(r) - lgamma(n - r + 1)))
 >   MC(function(x) {
 >        Fx <- pfun(x)
 >        con * Fx^(r - 1) * (1 - Fx)^(n - r) * dfun(x)
 >      },
 >      list("con"=con, "pfun"=pfun, "dfun"=dfun, "r"=r, "n"=n))
 > }
 > 
 > > pdf.order(9, 5, pnorm, dnorm)(0)
 > [1] 0.981772
 > 
 > This isn't as clean as in R but it is clearer in its intent,
 > to me at least, than the eval/substitute stuff.
 > 
 > The definition of MC (make closure) I use is not based on
 > substitute.  Substitute, no matter how sophisticated, is in my
 > view fundamentally the wrong tool for this job since it does
 > not understand syntax.  (It is a reasonable tool in this case
 > since the function is very small, but in general it is not).
 > Both S and R have syntactic constructs that can result in
 > shadowing of variables -- substitute cannot understand these
 > and will mess them up.  An alternative is to add bindings for
 > free variables as additional arguments to the function; in
 > this example this produces
 > 
 > > pdf.order(9, 5, pnorm, dnorm)
 > function(x, con = 630,
 >             pfun = function(q, mean = 0, sd = 1) { ... },
 >             dfun = function(x, mean = 0, sd = 1) { ... },
 >             r = 5,
 >             n = 9)
 > {
 >         Fx <- pfun(x)
 >         con * Fx^(r - 1) * (1 - Fx)^(n - r) * dfun(x)
 > }

This suggests that for my glm.nb example the MC solution would
not work and substitute really is necessary.  In that case it is
important that the generated function does not have additional
arguments, even with default values, since the functions
themsleves may be passed on to someone else's language modifiers
(to wit the robust() function, but not always) which make the
strong assumption of what arguments are present.

Never mind, in that case I already have a working solution using
substitute().  Now it seems an R solution is not only possible,
but simpler than that of S.

 > This isn't perfect (real lexical scope is much better) but, in
 > my opinion, it is a better approach than using substitute.

The only reservation I would have is whether the frozen
environment (explicitly in R, as extra arguments with defaults in
S) can get very large.  You would need to use substitute if you
wanted a result with pointers to whatever you used for pfun and
dfun and which changed if those (herer pnorm and dnorm) changed.
Not a good practice, I agree, but a real differnece between the
two approaches.

 > Here is the definition of MC I use to implement this.  It is
 > fairly convoluted, but once you have it and understand
 > conceptually what it does, which is quite simple, you don't
 > need to look at it again.
 > 
 > > MC
 > function(f, env = NULL)
 > {
 >         env <- as.list(env)
 >         if(mode(f) != "function")
 >                 stop(paste("not a function:", f))
 >         if(length(env) > 0 && any(names(env) == ""))
 >                 stop(paste("all arguments are not named:", env))
 >         fargs <- if(length(f) > 1) f[1:(length(f) - 1)] else NULL
 >         fbody <- f[length(f)]
 >         cf <- c(fargs, env, fbody)
 >         mode(cf) <- "function"
 >         return(cf)
 > }

Very illuminating.

 > Just my 2c worth :-)

And well worth both of them, even more.  :-) I think I'll frame
it and put it up on the wall as a reminder.

I'm glad I raised this issue now.  I don't mind being wrong if
the result is a useful clarification for us lesser mortals.  I
just wish it wasn't always so necessary for me to learn
everything out in public like this...

Thanks,
Bill
-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
r-help mailing list -- Read http://www.ci.tuwien.ac.at/~hornik/R/R-FAQ.html
Send "info", "help", or "[un]subscribe"
(in the "body", not the subject !)  To: r-help-request@stat.math.ethz.ch
_._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._._