From: "jeremyevans0 (Jeremy Evans)" Date: 2021-09-23T02:04:13+00:00 Subject: [ruby-core:105390] [Ruby master Feature#18035] Introduce general model/semantic for immutable by default. Issue #18035 has been updated by jeremyevans0 (Jeremy Evans). ioquatix (Samuel Williams) wrote in #note-17: > @jeremyevans0 your general model makes sense to me and I admire your approach to freezing the runtime. Can you explain where the performance advantages come from? Also: Performance advantages come from two basic ideas: 1) If objects are frozen, it opens up additional opportunities for caching them. With mutable objects, caching is very tricky. You can obviously clear caches when you detect mutation of the current object, but if you can mutate the objects held in the cache, then cache invalidation is very hard to get right. Purely immutable objects don't support internal caching, since an immutable cache is worthless, so this approach relies on let's say mostly immutable objects. Sequel datasets use this approach. They are always frozen, and take heavy advantage of caching to reduce the amount of work on repetitive calls. This is what allows you to have call chains like `ModelClass.active.recent.first` that do almost no allocation in subsequent calls in Sequel, as both the intermediate datasets and the SQL to use for the call are cached after the first call. 2) When freezing a class, you can check if the default implementation of methods has not been modified by checking the owner of the method. If the default implementation of methods has not been modified, you can inline optimized versions for significantly improved performance. Roda uses this approach to improve routing and other aspects of its performance. > > and freeze objects inside #initialize to have all instances be frozen > > Doesn't this break sub-classes that perform mutable initialisation? It doesn't break subclass initialization, as long as the subclass mutates the object in `#initialize` before calling `super` instead of after. Alternatively, you can have `freeze` in the subclass check for partial initialization, and finish initialization before calling `super` in that case. ---------------------------------------- Feature #18035: Introduce general model/semantic for immutable by default. https://bugs.ruby-lang.org/issues/18035#change-93803 * Author: ioquatix (Samuel Williams) * Status: Open * Priority: Normal ---------------------------------------- It would be good to establish some rules around mutability, immutability, frozen, and deep frozen in Ruby. I see time and time again, incorrect assumptions about how this works in production code. Constants that aren't really constant, people using `#freeze` incorrectly, etc. I don't have any particular preference but: - We should establish consistent patterns where possible, e.g. - Objects created by `new` are mutable. - Objects created by literal are immutable. We have problems with how `freeze` works on composite data types, e.g. `Hash#freeze` does not impact children keys/values, same for Array. Do we need to introduce `freeze(true)` or `#deep_freeze` or some other method? Because of this, frozen does not necessarily correspond to immutable. This is an issue which causes real world problems. I also propose to codify this where possible, in terms of "this class of object is immutable" should be enforced by the language/runtime, e.g. ```ruby module Immutable def new(...) super.freeze end end class MyImmutableObject extend Immutable def initialize(x) @x = x end def freeze return self if frozen? @x.freeze super end end o = MyImmutableObject.new([1, 2, 3]) puts o.frozen? ``` Finally, this area has an impact to thread and fiber safe programming, so it is becoming more relevant and I believe that the current approach which is rather adhoc is insufficient. I know that it's non-trivial to retrofit existing code, but maybe it can be done via magic comment, etc, which we already did for frozen string literals. -- https://bugs.ruby-lang.org/ Unsubscribe: