Use Immutable Objects
Tags: perl, oop
Published: 2021-04-20 06:54:25

# Immutable Objects

I’ve been spending time designing [Corinna](https://github.com/Ovid/Cor/wiki), a new object system to be shipped with the Perl language. Amongst its many features, it’s designed to make it easier to create immutable objects, but not everyone is happy with that. For example, consider the following class:

```perl
class Box {
    has ($height, $width, $depth) :reader :new;
    has $volume :reader = $width * $height * $depth;
}

my $original_box = Box->new(height=>1, width=>2, depth=>3);
my $updated_box  = $original_box->clone(depth=>9);  # h=1, w=2, d=9
```

Because none of the slots have a `:writer` attribute, there is no way to mutate this object. Instead you call a `clone` method, supplying an overriding value for the constructor argument you need to change. The `$volume` argument doesn’t get copied over because it’s derived from the constructor arguments.

[But not everyone is happy with this approach](https://github.com/Ovid/Cor/issues/8). Aside from arguments about utility of the `clone` method, the notion that objects should be immutable by default has frustrated some developers reading the Corinna proposal. Even when I point out just adding a `:writer` attribute is all you need to do to get your mutability, people still object. So let’s have a brief discussion about immutability and why it’s useful.

But first, here’s my last 2020 Perl Conference presentation on Corinna (at the time, called "Cor").

{% youtube Tst-vs4Br2A %}

# The Problem

Imagine, for example, that you have a very simple `Customer` object:

```perl
my $customer = Customer->new(
    name      => "Ovid", 
    birthdate => DateTime->new( ... ),
);
```

In the code above, we’ll assume the `$customer` can give us useful information about the state of that object. For example, we have a section of code guarded by a check to see if they are old enough to drink alcohol:

```perl
if ( $ovid->old_enough_to_drink_alcohol ) {
    ...
}
```

The above looks innocent enough and it’s the sort of thing we regularly see in code. But then this happens:

```perl
if ( $ovid->old_enough_to_drink_alcohol ) {
    my $date = $ovid->birthdate;
    ...
    # deep in the bowels of your code
    my $cutoff_date = $date->set( year => $last_year ); # oops!
    ...
}
```

We had a guard to ensure that this code would not be executed if the customer wasn’t old enough to drink, but now in the middle of that code, due to how `DateTime` is designed, someone’s set the customer birth date to last year! The code, at this point, is probably in an invalid state and its behavior can no longer be considered correct.

But clearly no one would do something so silly, would they?

# Global State

We’ve known about [the dangers of global state](https://softwareengineering.stackexchange.com/questions/148108/why-is-global-state-so-evil) for a long time. For example, if I call the following subroutine, will the program halt or not?

```perl
sub next ($number) {
    if ( $ENV{BLESS_ME_LARRY_FOR_I_HAVE_SINNED} ) {
        die "This was a bad idea.";
    }
    return $number++;
}
```

You literally _cannot_ inspect the above code and tell me if it will `die` when called because you cannot know, by inspection, what the `BLESS_ME_LARRY_FOR_I_HAVE_SINNED` environment variable is set to. This is one of the reasons why global environment variables are discouraged.

But here we’re talking about _mutable_ state.  You don’t want the above code to die, so you do this:

```perl
$ENV{BLESS_ME_LARRY_FOR_I_HAVE_SINNED} = 0;
say next(4);
```

Except that now you’ve altered that mutable state and anything else which relies on that environment variable being set is unpredicatable. So we need to use `local` to safely change that in the local scope:

```perl
{
    local $ENV{BLESS_ME_LARRY_FOR_I_HAVE_SINNED} = 0;
    say next(4);
}
```

Even that is not good because there’s no indication of why we’re doing this but at least you can see how we can safely change that global variable in our local scope.

# ORMs

And I can hear your objection now:

> “But Ovid, the DateTime object in your first example isn’t global!”

That’s true. What we had was this:

```perl
if ( $ovid->old_enough_to_drink_alcohol ) {
    my $date = $ovid->birthdate;
    ...
    # deep in the bowels of your code
    my $cutoff_date = $date->set( year => $last_year ); # oops!
    ...
}
```

But the offending line should have been this:

```perl
    # note the clone().
    my $cutoff_date = $date->clone->set( year => $last_year );
```

This is because the `set` method mutates the object in place, causing _everything holding a reference to that object to silently change_. It’s not global in the normal sense, but this [action at a distance is a source of very real bugs](https://rjbs.manxome.org/rubric/entry/1929).

It’s a serious enough problem that [DateTime::Moonpig](https://metacpan.org/pod/DateTime::Moonpig) and [DateTimeX::Immutable](https://metacpan.org/pod/DateTimeX::Immutable) have both been written to provide immutable `DateTime` objects, and that brings me to [DBIx::Class](https://metacpan.org/release/DBIx-Class), an excellent ORM for Perl.

As of this writing, it’s been around for about 15 years and provides a component called [DBIx::Class::InflateColumn::DateTime](DBIx::Class::InflateColumn::DateTime). This allows you to do things like this:

```perl
package Event;
use base 'DBIx::Class::Core';
 
__PACKAGE__->load_components(qw/InflateColumn::DateTime/);
__PACKAGE__->add_columns(
  starts_when => { data_type => 'datetime' }
  create_date => { data_type => 'date' }
);
```

Now, whenever you call `starts_when` or `create_date` on an `Event` instance, you’ll get a `DateTime` object instead of just the raw string from the database. Further, you can _set_ a `DateTime` object and not worry about your particular database’s date syntax. It _just works_.

Except that the object is mutable and we don’t want that. You can fix this by writing your own `DBIx::Class` component to use immutable `DateTime` objects.

```perl
package My::Schema::Component::ImmutableDateTime;

use DateTimeX::Immutable;
use parent 'DBIx::Class::InflateColumn::DateTime';

sub _post_inflate_datetime {
    my ( $self, @args ) = @_;
    my $dt = $self->next::method(@args);
    return DateTimeX::Immutable->from_object( object => $dt );
}

1;
```

And then load this component:

```perl
__PACKAGE__->load_components(
    qw/+My::Schema::Component::ImmutableDateTime/
);
```

And now, when you fetch your objects from the database, you get nice, immutable `DateTime`s. And it will be interesting to see where your codebase fails!

Does all of this mean we should never use mutable objects? Of course not.  Imagine creating an immutable cache where, if you wanted to add or delete an entry, you had to clone the entire cache to set the new state. That would likely defeat the main purpose of a cache: speeding things up. But in general, immutability is a good thing and is something to strive for. Trying to debug  why code far, far away from your code has reset your data is not fun.