4 ms·
In Go pretty much everything is very much mutable. The part that distinguishes Go is exactly the M:N scheduler used for both evented IO and parallelisation of c
by julik 6y ago
In Go pretty much everything is very much mutable. The part that distinguishes Go is exactly the M:N scheduler used for both evented IO and parallelisation of compute.
For _concurrency_ (what the Ruby fiber schedulers are for) what is proposed is probably quite a good idea. We can do as much hatin' as we want on JavaScript but its concurrency story has been much more successful than Ruby's due to evented IO as well - even though everything is mutable. As far as Ractor is concerned - well, WebWorkers are kind of similar - they get a separate thread, but you can only send them "messages" which must be immutable. How the MRI team is going to tackle mutations in those messages is an interesting excercise, because Ruby is ultimately a _live runtime_ - i.e. most of the things can change at any time. Not because it is made for sloppy programming but because it is made for incremental changes to the VM like Smalltalk.
- derefr 6y ago> How the MRI team is going to tackle mutations in those messages Object.freeze, presumably. Immutability exists as an option within Ruby’s OOP system, and has since the beginning, so it’s well-integrated[1], with other language features having been built to take it into consideration. I would expect any[2] message sent to a Ractor to be 1. On the sending side, verified as being acyclic; 2. On the receiving side, reconstructed by doing a recursive “clone and then freeze” operation. Since most messages wouldn’t be “deep”, this wouldn’t usually be costly; but in the case of a “deep”(ly hierarchical) message, you could probably pre-“deep-clone-and-freeze” the objects you’re going to send, and the runtime would hopefully detect this and not bother to deep-clone-and-freeze them again. (Maybe they’d add a runtime tag-bit on objects for “me and all my references — transitively! — are frozen”, that could be pre-set for any object when it’s frozen, if it sees that all its instance-variables are also already marked as transitively-frozen.) Alternately, they might just lean on the Marshal module, adding the capability for Marshal to load a dumped object-graph as all-frozen; and then just use Marshal.dump on the sending side and Marshal.load(..., freeze: true) on the receiving side. This wouldn’t allow for the efficiency wins of the above, but it would have the advantage of being a single already-well-tested-and-optimized all-in-C transformation; and it would give the MRI maintainers some docs to point at (those of Marshal.dump) to indicate what’s possible to send in a Ractor message. ————— [1] Integers, Floats, and Symbols come frozen by default; and there’s a pragma you can add to source files such that their string-literals will also be created frozen. So it’s not like Ruby code isn’t “prepared” for frozen objects. Your own Ruby code deals with plenty of them! [2] What if you want a mutable reference to an object in another thread? Well, think about the semantics of what you want. Presumably, you want sending a message to the object to actually first send (a recursive frozen clone) of your message back to the object’s owner-Ractor to handle. What system already does this? DRb! You can send a DRb object-proxy handle to another Ractor, and it won’t matter that it receives a recursively-frozen clone of it; it’ll still work to communicate with your own Ractor’s mutable “remote” object. Think of DRb handles as the Erlang PID-objects of Ruby.
- julik 6y agoIndeed, but I don't see as much issue with things which are freeze-able. You can Marshal.dump cyclic structures already with no issue. What seems to be the culprit at the moment is changing the method definitions / module composition of modules which participate in exchanges between Ractors. A lot of Ruby modules define constants dynamically, prepend modules to patch missing or broken functionality on existing modules, modify constants to patch bugs. Since there is no compile step / monomorphisation ahead of time it is likely that a program will be evolving after the VM has started and all the code has been loaded. I do not see the Ractor messaging setup covering it well at the moment. Maybe Ractor messages should be their own type which is only allowed to contain marshalable objects, and inside a Ractor the VM must be able to "replay" the same code structure mutations as the the entire VM (or those mutations must be cloned into the Ractor where appropriate). While a lot of people scream that "you should not monkeypatch" (and it is pretty much always a good idea not to) it is not always practical, given the fact that a lot of software produced is imperfect and does need careful patching sometimes, and Ruby is great at allowing it.