Minimizing Mutable State and Reducing Side Effects
Although Ruby is object-oriented, and therefore relies heavily on
mutable state, we can write nondestructive code in Ruby. In fact, many of
our Enumerable methods are inspired by
this.
For a trivial example, we can consider the use case for Enumerable#map. We could write our own naive map
implementation rather easily:
def naive_map(array)
array.each_with_object([]) { |e, arr| arr << yield(e) }
endWhen we run this code, it has the same results as Enumerable#map, as shown here:
>> a = [1,2,3,4]
=> [1, 2, 3, 4]
>> naive_map(a) { |x| x + 1 }
=> [2, 3, 4, 5]
>> a
=> [1, 2, 3, 4]As you can see, a new array is produced, rather than modifying the
original array. In practice, this is how we tend to write side-effect-free
code in Ruby. We traverse our original data source, and then build up the
results of a state transformation in a new object. In this way, we don’t
modify the original object. Because naive_map() doesn’t make changes to anything
outside of the function, we can say that this code is
side-effect-free.
However, this code still uses mutable state to build up its return value. To truly make the code stateless, we’d need to build a new array every time we append a value to an array. Notice the difference between these two ways of adding a new element to the end of an array:
>> a => [1, 2, 3, 4] >> a = [1,2,3] => [1, 2, 3] >> b = a << 1 => [1, 2, 3, 1] >> a => [1, 2, 3, 1] >> c = a + [2] => [1, 2, 3, 1, 2] >> b => [1, 2, 3, 1] >> a => ...
Become an O’Reilly member and get unlimited access to this title plus top books and audiobooks from O’Reilly and nearly 200 top publishers, thousands of courses curated by job role, 150+ live events each month,
and much more.
Read now
Unlock full access