Would it be helpful to have Ruby collection objects such as Hash, Struct, and OpenStruct, include a freeze_deeply
method that freezes not only the object’s properties, but the objects referred to by those instance variables?
For example, even after freezing this struct, the string value can be appended to with <<
:
CityLocation = Struct.new(:name, :lat, :long)
city1 = CityLocation.new('New York', 0, 0)
city1.freeze
city1.name << ' City' # does not raise an error
If Struct had a freeze_deeply
method, this could be prevented:
def freeze_deeply
freeze
to_h.values.each(&:freeze)
self
end
There’s probably a much better way to get the values to freeze, but this works for illustration purposes. Regarding classes implementing Enumerable, one can of course use map(&:freeze)
, but it might encourage immutability in code to elevate this to its own named method Enumerable#freeze_deeply
or Enumerable#freeze_values
.
What do you think?
It was pointed out to me by Erol Fornales that the subject of freezing deeply is already discussed at Feature #2509: Recursive freezing? - Ruby master - Ruby Issue Tracking System.
However, this is a slightly different issue in that I am only suggesting freezing the values contained by the collection object, using freeze
, not attempting to deeply freeze those values.
I see now that my use of the word “deep” in the issue title and the method name was misleading.
Perhaps the method (an instance method of Hash, Array, Set, etc.) should instead be called freeze_values
.
Given that freeze
returns self, one could accomplish freezing both the collection and its values by chaining the two methods:
my_array.freeze.freeze_values
In my original post, I should have specified each
instead of map
. That is, I said "one can of course use map(&:freeze)
", but I should have said "one can of course use each(&:freeze)
".
I have posted a feature request for this to Feature #17627: Suggestion: Implement `freeze_values` instance method on collection-like classes. - Ruby master - Ruby Issue Tracking System.
I think my explanation of the issue is clearer there than here, and would like to delete this post.