This e-mail concerns this ticket:
http://reia.lighthouseapp.com/projects/19319/tickets/22-class-method-is-not-seen-inside-a-block
...and we're further down the rabbit hole.
In this case, Phil is trying to invoke a method from within a block, which is a perfectly reasonable thing to do in a language like Ruby. However, the semantics of how this should operate in a language like Reia are tricky.
What's so hard about calling a (local) method from a block, you may ask? Well, methods in Reia differ from functions because they can access (and modify) hidden state. This is yet another case of adding state to Erlang being, well, hard.
In order for a lambda to invoke a method, it needs to pass the current hidden state of an object to that method. The simplest solution to this is to close over the hidden state at the time the lambda is created, then pass it to the method. However, this means if we declare a lambda, alter the hidden state, then call the lambda, any method invocations will operate on the old state of the object and will not reflect the latest changes.
What do we do if the method "mutates" the hidden state? Imagine we pass this lambda (or block) as an argument to a method in a different object. The lambda/block will try to call a method in the first object, and make alterations to its state. We've now stumbled upon a case of one object trying to alter the state of another through side effects.
My inclination as to the best way to proceed would be:
1) Allow lambdas/blocks to reference hidden state/instance variables, but close over them at the time the lambda/block is declared. In the case of a lambda it means it will close over the state at the time the lambda is declared, and any references to instance variables or method invocations from the lambda will work off this "old version" of the state, which may be different than the state at the time the lambda is invoked.
2) Disallow assignment to instance variables within lambdas/blocks. As soon as you're within the scope of a lambda/block "mutations" to the outer hidden state are disallowed. This is similar to restrictions on mutating the values of outer variables. Lambdas/blocks can only access snapshots of the state at the time they are declared. Anything you want to do with that state from there must be handled in a pure functional manner (e.g. by returning new values you wish to bind from the lambda itself).
So... don't do this!
foo() do
@x = 42
Do this instead:
@x = foo() do
42
The compiler can easily detect assignment to an instance variable within a block so these sorts of errors could be caught at compile time.
3) Allow blocks/lambdas to invoke methods BUT if those methods attempt to mutate any instance variables, then crash. This means a programmer won't know until runtime that their code is attempting to illegally "mutate" the hidden state. Methods will still be able to access the hidden state, but they'll work off a copy of it at the time the lambda/block was declared. However, as far as I can tell there is no safe way to allow destructive updates to the state within a lambda/block since we don't have guarantees about where the lambda will execute.
There is one particular case where I can see a lambda/block safely mutating instance variables, and that would be in a case similar to Ruby's Object#intsance_eval/instance_exec. Here we are handing an object a lambda/block and telling it "execute this against your own state rather than mine". In this case no ivars would be closed over at all. Instead the block invocation would accept the object's current state and could return a new state. This is essentially the same thing as letting a block/lambda act in place of a method.
I have not yet decided if I want to support instance_eval/instance_exec.
--
Tony Arcieri