I think I've at least made a start now.
···
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
-------------------------------------------------------------------------
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 <whatever> ... }
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.