From murdoch at stats.uwo.ca  Fri May 22 19:04:56 2009
From: murdoch at stats.uwo.ca (Duncan Murdoch)
Date: Fri, 22 May 2009 13:04:56 -0400
Subject: [Rd] Scope problem?
In-Reply-To: <d8ad40b50905220928p5235b38fi69361e4c7f7cd91d@mail.gmail.com>
References: <d8ad40b50905220928p5235b38fi69361e4c7f7cd91d@mail.gmail.com>
Message-ID: <4A16DB38.5010803@stats.uwo.ca>

On 5/22/2009 12:28 PM, Barry Rowlingson wrote:
> I've just spent today trying to fix a Heisenbug...
> 
> this function returns a linear interpolator function:
> 
> interpOne <- function(xl,yl){
>   f = function(data){
>     t = (data-min(xl))/(max(xl)-min(xl))
>     return(min(yl)+t*(max(yl)-min(yl)))
>   }
>   return(f)
> }
> 
>> k=interpOne(c(0,1),c(4,5))
>> k(0.5)
> [1] 4.5
> 
> and this function uses the above to return a function that returns a
> piece-wise linear interpolator function:
> 
> mr <- function(){
>   parts = list()
>   ranges =  rbind(c(0,1),c(1,2),c(2,3))
>   domains = rbind(c(3,4),c(5,6),c(2,8))
>   for(i in 1:length(ranges[,1])){
>     parts[[i]] = interpOne(ranges[i,],domains[i,])
>   }
> 
>   f = function(d){
>     pos = sum(d>ranges[,1])
>     cat("using pos = ",pos,"\n")
>     return(parts[[pos]](d))
>   }
>   return(f)
> }
> 
> m = mr()
> 
>  The 'ranges' and 'domains' vectors describe the pieces. But this doesn't work:
>> m(0.5)
> using pos =  1
> [1] -7
> 
>  - but it should be 3.5 (since 0.5 is in the first piece, and that
> then interpolates between 3 and 4). What about the other pieces:
> 
>> m(1.5)
> using pos =  2
> [1] -1
>> m(2.5)
> using pos =  3
> [1] 5
> 
>  - which looks like it's using the last set of range/domain pairs each
> time. Curious, I thought.
> 
>  So I thought I'd evaluate the functions as they are created in the
> list to see what's going on. Change the loop to print out:
> 
>   for(i in 1:length(ranges[,1])){
>     parts[[i]] = interpOne(ranges[i,],domains[i,])
>     cat("part ",i," at zero = ",parts[[i]](0),"\n")
>   }
> 
> and try:
> 
>  > m=mr()
>  part  1  at zero =  3
>  part  2  at zero =  4
>  part  3  at zero =  -10
> 
>  looks good, those are the intercepts of my pieces... but now:
> 
>  > m(0.5)
>  using pos =  1
>  [1] 3.5
>> m(1.5)
> using pos =  2
> [1] 5.5
>> m(2.5)
> using pos =  3
> [1] 5
> 
>  Woah! It's now working!  Trying to observe the thing changes it? A Heisenbug!
> 
>  I can only think it's my misunderstanding of some aspect of R's
> scoping and evaluation rules. Does evaluating the functions within
> that loop cause a copy of some environment to be made, or a 'lazy
> evaluation' to be evaluated? Or a 'promise' to be fulfilled? I don't
> really understand those terms, I'd just hoped functions ran in the
> environment they were created in. Seems sometimes they do, sometimes
> they dont... What's going on?

I think it's lazy evaluation that gets you. I haven't stepped through 
your code, but have done a similar one recently, and this is my guess 
about what happens:

- interpOne creates the function, but never evaluates xl or yl.

- You call it several times, to create a number of functions, but still 
never evaluate xl or yl.  They are left as promises to evaluate 
ranges[i,] and domains[i,] in the environment of that loop.

- Finally, you start evaluating those created functions, and it's at 
that point that xl and yl get forced.  Since i is now on the last value, 
they get the wrong values set.


Putting force(xl); force(yl) into your interpOne definition (so they get 
executed when interpOne is called, not just when the returned function 
is called) should work.

Duncan Murdoch



[Rd] Scope problem?
Duncan Murdoch