Feature #17627
openSuggestion: Implement `freeze_values` instance method on collection-like classes.
Description
Suggestion: Implement freeze_values
instance method on collection-like classes.
By collection-like classes, I mean classes such as Array, Hash, Set, Struct, OpenStruct, and custom classes containing multiple objects.
There has been some discussion of a recursive deep_freeze
method, and although it could be very useful, there are potential problems regarding unintended consequences, and guarding against these would make the implementation more complex.
This complexity could be greatly reduced if we limit the scope of the freeze to only one level, and have the new freeze_values
method call freeze
.
The implementation would be trivial, I think. For example:
class Array # same for Set
def freeze_values
each(&:freeze)
end
end
class Hash
def freeze_values
values.each(&:freeze)
end
end
There would still be a risk that the programmer would call this when some values should not be frozen, but that risk would be smaller and more manageable.
Also, there are many cases in which these collections contain simple objects such as strings and numbers. In these cases, recursion would not be necessary or helpful.
Although it could be argued that the implementation is so trivial that it does not need a method implemented, I believe that:
- the method would nevertheless simplify the task, encouraging freezing
- the method name would be a higher level description of the operation, making the code more readable
- custom classes could implement this contract in their own way, yielding the benefits of polymorphism
What do you think?
Updated by keithrbennett (Keith Bennett) over 3 years ago
Discussion of the related but different deep_freeze
feature suggestion is at https://bugs.ruby-lang.org/issues/17145.
Updated by marcandre (Marc-Andre Lafortune) over 3 years ago
I do not see the use case, especially now that we have two safe ways to deep freeze:
-
Ractor.make_shareable
-
# shareable_constant_value: literal
Updated by keithrbennett (Keith Bennett) over 3 years ago
I don't think the Ractor#make_shareable
method and the shareable_constant_value
pragma address this issue, for a few reasons:
-
they do a recursive deep freeze;
freeze_values
is just for a single level freeze; that is, callfreeze
on the contained objects, such as an array's elements or a hash's values, and let those objects decide based on their freeze implementation how deep to go. Doing a deep_freeze can be risky, whereas freezing only one level down is much less so. This may not be sufficient freezing to be ractor-safe, but that is not necessarily the intention offreeze_values
. -
Ractor#make_shareable
is ractor-specific, whereas there may be many other use cases not related to ractors that would require freezing the object's values.Ractor#make_shareable
would be hard to find for a developer not using ractors and not familiar with them, who is looking for a way to freeze an object's values. In addition,make_shareble
is less intention revealing thanfreeze_values
when not used in the context of ractors. -
the
shareable_constant_value
pragma only applies to constants, and applies to all constants in a file, whereas the kinds of places wherefreeze_values
would likely be called go far beyond that one use case, and may not need to be called for all objects of the given type. For example, given two hashes having values that must not be changed, it would be helpful to freeze the values if it were passed all over the code base with a relatively long life, whereas this might not be necessary for a hash whose lifetime was limited to the method call in which it was created, and passed only to known safe methods. (Though even when passed to known safe methods, it might be better to freeze the values, given that gems and other code can monkey patch that method's calls.)
Updated by marcandre (Marc-Andre Lafortune) over 3 years ago
Could you provide some actual real-world use cases?