Sunday, December 13, 2009

Reia: now with "Magic Rebinding"

In Ruby, thanks to its first class syntax for "hashes" and mutable state, it's quite easy to do:
h = {}
h[key] = value
The equivalent code in Erlang is noisier, thanks to immutable state and single assignment:
Dict1 = dict:new(),
Dict2 = dict:store(Key, Value, Dict1).
Since Reia lacks mutable state, it never before had syntax as simple as Ruby's... but now it does!

I have been trying to hold off on adding syntactic sugar like this to my new "minimalist" branch of Reia. However, this is a feature I meant to add to the old implementation, and tried to retrofit it in long after the implementation had grown quite complex, never managing to succeed. I decided to tackle it now, and I'm happy to announce that it works! Furthermore, it can be used in complex pattern matching expressions:
>> m = {}
=> {}
>> (m[:foo], m[:bar], m[:baz]) = (1,2,3)
=> (1,2,3)
>> m
=> {:bar=>2,:baz=>3,:foo=>1}
So what is going on here exactly? Reia is an immutable state language, so surely I'm not mutating the value that "m" references.

In these cases, Reia is "altering" the local variable binding. Each time you change a member of a map ("hash" for you Ruby folks, "dict" for you Erlang folks), a new version of that map is calculated, then bound to "m". Behind the scenes, the Reia compiler is translating these calls into destructive assignments.

Maps, Tuples, and even Lists now support assignments in this way (although Lists only for consistency's sake... I hate to think of people actually setting values in lists by index). Tuples and Lists even support Ruby-style negative indexing:
>> t = (1,2,3)
=> (1,2,3)
>> t[-1] = 42
=> 42
>> t
=> (1,2,42)
I plan on eventually exposing this functionality to user-defined types as well, in the form of "bang methods" on immutable objects. Users of Ruby are likely familiar with them:
>> arr = [1,2,3]
=> [1,2,3]
>> arr.reverse; arr
=> [1,2,3]
>> arr.reverse!; arr
=> [3,2,1]
Here you can see that calling the "reverse" method on an array (without the !) does not modify the original array in-place. Instead, it returns a new array in reverse order. The complimentary "reverse!" method performs an in-place modification of the array.

The "method!" idiom in Ruby is generally used to indicate methods that modify their receivers as opposed to versions which do not. However this is not a solid requirement, and "!" is often added to any methods considered "dangerous". There's no specific meaning to putting "!" on the end of a method and certainly nothing Ruby does differently at the language level.

In Reia, "bang methods" will be a first class language construct, and will always rebind the receiver with the return value of the method. This will provide a simple way to allow "in place" modifications of immutable objects, by having "bang methods" create and return a new immutable object.

It's the best of both worlds: the ease of use that comes from mutable state, with the concurrency benefits of being completely immutable.


Knodi said...

Reia is shaping out to be really good.

raggi said...

"!" methods in ruby don't strictly mean that they modify the receiver. "!" means the method is to be considered "more dangerous" than it's non-! counterpart.

David Black has an excellent and succinct statement on this, however, it's in a standalone html file somewhere on his domain, where I can't remember.

What I have to wonder is the motivation for this, it seems to be dangerous, at least there are some alarm bells going off in my mind - although I have not used it, so my intuition may be far wrong.

It seems to me that the idea of a method receiver having control of local variables for the scope in which the method was called, seems inverted. Moreover the gains that are made for immutability seem to be lost once this occurs. Sure this is only locals, but the same class of errors has just been opened from this as exist for general shared state using other variable types, it's just restricted to local scope. - That's all good until closures.

I'm not sure, I totally understand why you want it, and my intuition could be very wrong, but if it really turns out to be useful, I have to wonder why the hold on to immutability. I don't generally have issues with mutable state in other languages, because I am careful to architect around the issues that can be raised from it - here I get the feeling I'd be using the same kind of care the other way around. Paying for the cost of immutability seems undesirable when the programmer has to also put conceptual effort in.

Tony said...

Hi raggi,

Indeed you'll see I noted that Ruby's "bang methods" don't strictly modify the receiver but also perform "dangerous" actions in my post.

As implemented in Reia, "bang methods" do not directly allow methods to modify the binding. In fact, as presently called, the method invoked isn't even aware it's being used as a "bang method". In that regard, ! functions more like an operator in the local scope.

Regarding your comments as to abandoning immutability, ideally I'd like to (in favor of process-local mutable state, using COW when messages are sent between processes). However this would require a far different virtual machine than BEAM.

A great deal of optimization work has already gone into Erlang, and I am trying to build on that. So, for now, I am stuck with immutability.

uwanna said...

gucci replica handbags
men gucci shoes
Gucci men sneakers
Gucci men moccasins
gucci women sneakers
gucci women boots
Gucci men boots
Gucci shop
Gucci bags
Gucci shoes
wholesale gucci shoes
cheap Gucci handbags
Gucci ON sale
Gucci Belts
Gucci small accessories
Gucci hats & scarves
Gucci wallets
Gucci Handbags
Women Gucci shoes
Men Gucci shoes
discount gucci shoes
cheap Gucci shoes