[R] Lazy Evaluation?

John Chambers jmc at research.bell-labs.com
Tue Jun 8 20:52:28 CEST 2004


No, not lazy evaluation.  The explanation has to do with environments
and how callGeneric works.

It's an interesting (if obscure) example.

Here's the essence of it, embedded in some comments on debugging and on
style.  (This will be a  fairly long discussion, I'm afraid.)

A useful starting point in debugging is often to look at the objects
involved.  In this case there are two versions of a method that
generates an object containing a function.

In the first version, which works, the object generated in the example
is:

> sinNF
An object of class "NumFunction"
Slot "fun":
function(n) nfun(n)
<environment: 0x2f5c268>

In the second, which gets into an infinite loop, it is:

> sinNF
An object of class "NumFunction"
Slot "fun":
function(n) callGeneric(x at fun(n))
<environment: 0x3ada884>

The first version is totally inscrutable to the user who looks at the
object.

But a little thinking will suggest why the second one fails.  The
`callGeneric' function is meant to be used inside a method definition. 
But sinNF at fun isn't a method; it's not obvious what callGeneric should
do but it probably won't be good.

(It's arguagle that it should fail right away because the function being
used is not a generic.  But even legitimate uses of callGeneric can
accidentally get into infinite loops by in effect ending up with the
same method on the same data.)

We can verify the infinite loop by using the trace() function to catch
calls to callGeneric.

> trace(callGeneric, recover)

Then:

> sinNF at fun(sqrt(pi/2))
Tracing callGeneric(x at fun(n)) on entry 

Enter a frame number, or 0 to exit   
1:sinNF at fun(sqrt(pi/2)) 
2:callGeneric(x at fun(n)) 
Selection: 0
Tracing callGeneric(x at fun(n)) on entry 

Enter a frame number, or 0 to exit   
1:sinNF at fun(sqrt(pi/2)) 
2:callGeneric(x at fun(n)) 
3:eval(call, sys.frame(sys.parent())) 
4:eval(expr, envir, enclos) 
5:sinNF at fun(x at fun(n)) 
6:callGeneric(x at fun(n)) 

and so on.

But why does the first version work?  Because that nfun function is
defined inside another function where a local definition of the name of
the generic is stored in its environment.  So the call from nfun to
callGeneric does find the generic (sin in this case).  To figure out
that this happens however, would require a lot of analysis.

This is definitely NOT the sort of arcane knowledge users are expected
to apply.

There was a discussion on this mailing list recently of the recommended
style that could be called "functional".  The definition of a function
should make sense on its own and, in particular, should avoid depending
on external objects, particularly external objects that may be changed. 
The methods in the example are very far from this style.

Both versions of sinNF are essentially incomprehensible on their own,
and the one that works more so.  Not meant as a criticism, it was
remarkable that you arrived at a version that did work.

But if possible one would like to get to the same effect with an object
that makes sense.  In this example, it's possible to use some of the
same information to construct an object that says what it does.  I think
you want to compose the function currently in the object with the
function you're calling.  By making the computation a method for the
Math group generic, you get all the math functions to work this way in
one step (clever, though rather unintuitive for the user).

To produce a clearer (and more efficient) version, get the .Generic
evaluated when the object is created; e.g., 

setMethod("Math", 
          "NumFunction",
          function(x){
            f <- function(n){g <- gg; f(g(n))}
            body(f) <- substitute(
                          {g <- gg; f(g(n))},
                           list(f = as.name(.Generic), gg = x at fun))
            NumFunction(f)
          })

Then the object sinNF makes sense to the user:

> sinNF
An object of class "NumFunction"
Slot "fun":
function (n) 
{
    g <- function (x) 
    x^2
    sin(g(n))
}
<environment: 0x384c8e8>

> sinNF at fun(sqrt(pi/2))
[1] 1

Thomas Stabla wrote:
> 
> Hello,
> 
> I've stumbled upon following problem, when trying to overload the methods
> for group Math for an S4-class which contains functions as slots.
> 
>   setClass("NumFunction", representation = list(fun = "function"))
> 
>   NumFunction <- function(f) new("NumFunction", fun = f)
> 
>   square <- function(x) x^2
>   NF <- NumFunction(square)
> 
>   setMethod("Math",
>             "NumFunction",
>             function(x){
>                 nfun  <- function(n) callGeneric(x at fun(n))
>                 tmp <- function(n) nfun(n)
>                 NumFunction(tmp)
>             })
> 
>   sinNF <- sin(NF)
>   sinNF at fun(sqrt(pi/2))
> 
> # works as expected, returns 1
> 
> # now a slightly different version of setMethod("Math", "NumFunction",
> # ...), which dispenses the "unnecessary" wrapper function tmp()
> 
>   setMethod("Math",
>             "NumFunction",
>             function(x){
>                 nfun  <- function(n) callGeneric(x at fun(n))
>                 return(NumFunction(nfun))
>             })
> 
>    sinNF <- sin(NF)
>    sinNF at fun(sqrt(pi/2))
> 
> # produces an error, namely:
> # Error in typeof(fdef) : evaluation nested too deeply: infinite
> # recursion / options(expression=)?
> 
> Replacing the generating function NumFunction() with corresponding
> new(..) calls doesn't change the outcome.
> 
> When I call the newly defined functions nfun resp. tmp from within the
> function body of setMethod(), e.g.
> 
>   setMethod("Math",
>             "NumFunction",
>             function(x){
>                 nfun  <- function(n) callGeneric(x at fun(n))
>                 cat(nfun(1))
>                 NumFunction(nfun)
>             })
> 
> by tmp(1) resp. nfun(1), both versions "cat" correct output when called by
> sin(NF).
> 
> If I don't return NumFunction(tmp) resp. NumFunction(nfun) but tmp resp.
> nfun, both versions work just fine, i.e. sin(NF)(sqrt(pi/2)) returns 1.
> 
> Would someone explain me this behavior? (R Version 1.9.0)
> 
> Best regards,
> Thomas Stabla
> 
> Sourcecode can be found at:
> http://www.uni-bayreuth.de/departments/math/org/mathe7/DISTR/NumFun.R
> 
> ______________________________________________
> R-help at stat.math.ethz.ch mailing list
> https://www.stat.math.ethz.ch/mailman/listinfo/r-help
> PLEASE do read the posting guide! http://www.R-project.org/posting-guide.html

-- 
John M. Chambers                  jmc at bell-labs.com
Bell Labs, Lucent Technologies    office: (908)582-2681
700 Mountain Avenue, Room 2C-282  fax:    (908)582-3340
Murray Hill, NJ  07974            web: http://www.cs.bell-labs.com/~jmc




More information about the R-help mailing list