From: Brian Candler Date: 2003-02-04T07:46:44+09:00 Subject: Re: Local variables & blocks On Mon, Feb 03, 2003 at 02:23:51AM +0900, Brian Candler wrote: > > Block parameters are only way to create block local variables, but > > they still exists. If I read right, there's no block local variables > > including block parameters in your proposal, right? > > Right. > > Clearly there is some magical distinction between block parameters and > 'ordinary' variables, bound up somehow in closures - something which I've > never had a requirement for in real life. > > I'll need to take some time to get my head around it :-) I think I've at least made a start now. One thing I've realised is that Proc objects must have their own local variables - for their parameters, at least - in case they call themselves recursively. I can't see how to get a Proc object to refer to itself except by passing a reference to itself as one of its arguments, and I have not yet reached sufficient enlightenment to know when you'd actually want to do this, but here's a contrived example: ------------------------------------------------------------------------- def recursor(op) return proc do |me,acc,count| if count <= 0 acc else me.call(me, acc.send(op,count), count-1) end end end summer = recursor("+") puts summer.call(summer,0,10) # 10+9+8+... factorial = recursor("*") puts factorial.call(factorial,1,8) # 8*7*6*... ------------------------------------------------------------------------- In the function 'recursor', 'op' is a local variable scoped to the function, and me/acc/count are local variables scoped to the do..end block only, which get separate values each time the function invokes itself. The variable 'op' is bound to a value when 'recursor' is called, and remains bound to it: e.g. summer references a Proc object with op bound to "+" 'op' is only used in a read-only manner in the above example, but the current Ruby implementation does not stop it from changing it - in which case the behaviour of the Proc object could change on each call. The bound variables behave like hidden instance variables. So if you want to be perverse: ------------------------------------------------------------------------- def recursor(op) return proc do |me,acc,count| if count <= 0 if op=='+' then op='-' # swap the op elsif op=='-' then op='+' end acc else me.call(me, acc.send(op,count), count-1) end end end summer = recursor("+") puts summer.call(summer,0,10) >> 55 puts summer.call(summer,0,10) >> -55 ------------------------------------------------------------------------- Now, I am no computer scientist, but I'd like to know: is it generally considered that closures aren't supposed to carry state in this way? That's what objects and instance variables are for. Or is there a valid reason for the closure to be able to alter the environment it was created in? Something which the example above _doesn't_ highlight is the use of other variables defined locally to the block. Suppose I need to create some intermediate value 'i' while calculating: in general we'll need a fresh copy of 'i' for each recursive invocation. And in Ruby, if we accidentally already had a local variable called 'i' outside the block, then there will be a single value for 'i' shared between each iteration, and it will break. To make this example even more contrived: ------------------------------------------------------------------------- def recursor(op) #i=0 return proc do |me,acc,count| if count <= 0 i = 10 acc else i = 0 me.call(me, acc.send(op,count), count-1) + i end end end summer = recursor("+") puts summer.call(summer,0,10) ------------------------------------------------------------------------- This works, but if you uncomment the 'i=0' outside the block, it goes wrong, because you end up with a single value of 'i' shared between all the iterations. I think this is what makes me uncomfortable with Ruby at the moment: "i=0" might create either a block local variable (separate for each recursion into the function, or separate for each thread when we're talking about threads) - or it might be a static variable shared by them all - and the syntax is the same in both cases. One of the fundamentals of Ruby is that there are no declarations. Well, I don't think that's strictly true: "foo = 0" is a declaration, whether you like it or not, because it's an instruction to the compiler to treat subsequent uses of bareword "foo" as a local variable, rather than a method call to self.foo. But I can cope with one level of implicit declaration; it's the other levels of implicit declaration which I find unappealing. It seems to me that if you want to avoid declarations, implicit or explicit, then you need to eliminate the distinction which the declaration is expressing. Given that we must have block-local variables at least for block parameters, and ideally also for intermediate values calculated within a block, then here is where my thought train is leading: why not make *everything* block-local? By that, I mean that *all* the local variables, both inside and outside the block, get a fresh copy each time the block is invoked (and thus are restored when the block terminates), all held in the same frame. This appears to solve a number of problems at a stroke. All variables can be used recursively, whether or not they were previously used before the block; and all variables are thread-local. It would mean that closures couldn't alter their external environment as a way of carrying state, as described earlier. Unfortunately, it would probably also break the ability for code within a block to set a value which persists afterwards: found = false mylist.each do { .... found = true if ... } found and puts "Got it" and that is, of course, a very useful pattern. If it were possible to duplicate the local stack frame only when the proc is *re*invoked recursively, we might have a solution... that's more than enough rambling though :-) Regards, Brian.