Immutability with Moo(se)

Would it be doable, to have a module to activate immutability with Moo(se)? Something like

with MooX::Immutable;

which would make all the objects from the class immutable.

As a short introduction, immutability is when objects never change: instead of modifying data in place, you must make a copy of it and override the values you want to change. Thus when you use an object at any point in your code, it will always be exactly the same: you are never taking the chance that some method modified it without you realizing.

I've come across the concept of immutability a fair few times recently:


  • chromatic explains that it "reduces the possibility that someone will manipulate the object and make it invalid" and thus "you get to remove a lot of error checking code because wtihin the API you can assume that the object is always in a valid state"

  • DateTime::Moonpig is immutable because mutability is dangerous.

  • Tobyink says "Immutable objects should be favoured over mutable ones."

  • nothingmuch wrote (in 2009) three excellent articles about immutability, where s.he explains various reasons why immutability is good, and indicates ways to achieve it with Perl...

...but now we have Moo(se) so I'm hoping there can be a simpler way.

If I understood well, MooX::Immutable would need to clone every input and output:


  • input: all attributes that are passed as parameters to the contructor

  • output: all accessors return a clone of the corresponding attribute


And I think that... that'd be it? Am I missing something?

Would that be something doable? useful?

PS: after a byterock's first comment, I thought I should clarify what immutability is and what a reasonable goal could be.

Immutability is a way of programming, where you never modify a variable that has been assigned nor an object that has been created. It's generally considered a "good practice". Some languages like Haskell and Erlang are immutable by default: once a variable has some value it cannot change at all anymore. Clojure provides immutable data structures too, so it's easy to keep everything immutable. To understand why that's good, read some of the links I indicated above.

In Perl it's not that simple, because Perl is flexible. In the same way that Moose made it much easier to produce standard and clean object-oriented code, I'm hoping that we can come up with some tool(s) to facilitate the production of immutable code.

But the difficulty is that to produce immutable code, objects need to have all attributes immutable. And CPAN makes this difficult because not many modules will guarantee that they do everything immutable (for instance DateTime is not immutable; DateTime::Moonpig is the immutable version of DateTime).

Moose had a similar problem: how do you subclass a non-Moose class? And they came up with a solution: MooseX::NonMoose. Can we come up with solutions for immutability?

There seem to be two ways to go about it. Two solutions:

1. Try to only use immutable modules from CPAN... or use them, but don't use the methods that mutate state (if you can figure out and then remember which ones they are).

2. Clone all object inputs (constructor parameters) and outputs (accessor return values) so that the object cannot be modified (in theory that's perfect).

Toby pointed out the problem with solution 2:
- not all objects can be cloned easily (but most can - in the same way that not all classes can be used with MooseX::NonMoose, but most can)
- what happens when someone uses a mutable method on a DateTime attribute?
Well, it mutates their clone of the attribute but not the attribute itself. Which is what we want to protect immutability, but it could be confusing for the user. I'm still thinking though, that if you advertise your class as being immutable, as in "You won't be able to modify this object - accessors will only return clones of the attributes", users should be able to figure it out. But yeah, it's no ideal...

For solution 1 even though it's not perfect we could build some tools to help, like:
- a Moo(se)X module that makes ALL scalar, hashref and arraref attributes immutables (with Sub::Trigger::Lock)
- a module that makes it easy to test for immutability, so more modules can guarantee immutability on CPAN.

In an ideal world we could "immutabilize" an object, so that any attempt to modify it throws an exception. For example "$datetime->add_duration()" would throw an exception.
I have no idea whether this is possible...

12 Comments

If you make the assumption that all access to attributes is via accessor methods, and nobody is going to sneakily look at $instance->{"attr"}, then immutability is not that difficult to achieve.

Firstly, make sure all attributes are declared as is => "ro". That's half the battle already; attributes which are strings, numbers, booleans, etc will now be reasonably protected.

The remaining gap is references. Although the reference itself will be read-only, the thing being referred to (probably an array or hash) is still mutable. For example, for a read-only attribute which is an arrayref, we can still do this:

push @{ $instance->attr }, @more_things;

Note that this is exactly where the "always in a valid state" problem that chromatic mentions comes in. Perhaps we validated that attr was an arrayref of integers in the constructor; that push statement could be adding any old data onto the array.

The Perl internals have the ability to mark an array or hash as immutable, though this is not really documented. Const::Fast is one module that exposes this functionality, though the API of Const::Fast is not especially convenient for applying read-onlyness to Moose/Moo object attributes. Sub::Trigger::Lock is a module I wrote that is perhaps a little more convenient for use with object attributes. (I perhaps need to better document the gotcha that triggers aren't executed when an attribute value is provided by the default/builder sub.)

Another approach would be to simply not have any attributes which are hashrefs or arrayrefs. Instead, make the attributes be some kind of blessed set/collection/bag objects which provide their own accessor methods. List::Objects::WithUtils is a good place to start if you want to pursue this approach. List::Objects::Types even provides some nice coercions to List::Objects::WithUtils objects from plain old arrayrefs and hashrefs.

I don't think cloning is necessarily the best way to approach this. Yes, it's potentially a pretty thorough way to prevent people from tampering, but it has its own set of problems.

If all objects are plain old blessed hashrefs, cloning everything seems a simple enough proposition. Cloning $o is really just $clone = bless({%$o}, ref($o)). However, given that objects (even hashref-based ones) may store some of their attributes inside-out, this isn't a generic solution - it requires some co-operation from the object; it must provide some kind of $o->clone method for you to call.

Co-operative immutability is perhaps a better solution. If I have an attribute which takes DateTime objects, and it's documented as read-only, then I probably don't want you calling mutator methods on it. Perhaps you technically can, but I'd rather you didn't, the same way I'd rather you didn't access my attributes like $o->{"attr"}.

There are styles of programming that encourage people to co-operate. In your example where you create a DateTime object, pass it to my constructor, and then manipulate your original DateTime object, that could be combated by documenting that my constructor accepts an ISO-8601-formatted datetime string, and then coercing it to a DateTime object internally.

In your example of $object->attribute->some_mutator(), if $object->attribute just returned a clone of the attribute, yes, it would mean that $object has been made safe from mutation, but it also means that the call to some_mutator() has silently succeeded, but effectively probably done nothing, which leads to a counter-intuitive interface. I'd rather that call threw an exception. And if an exception is not a possibility, then I'd rather it just properly succeeded, mutating the object, but in documentation discourage callers from doing this sort of thing. (See also: the Law of Demeter, which if followed would probably forbid such a method call.)

Immutable instances require "buy-in" from across the entire application. Yes, I can write a class in such a way that it encourages immutable usage (e.g. making accessors read-only). However, this class is only a small part of the jigsaw that makes up the entire application. Perhaps some of the other classes in the application (like DateTime) might have mutable instances - the solution is to just agree to not call the methods that mutate instances - especially not on objects which might have other objects pointing to them and thus result in action at a distance. (If I create a DateTime object in a method, and don't pass it to anybody else, then I can probably safely call mutators on it within the privacy of my own homeHHHH lexical scope.)

@mascip: DateTime being mutable is a bad design. Just don't use mutable classes for the properties of the class you want to be immutable.

Yes, I know this is good in theory, but hard is practice due to the DateTime object we have in Perl 5.

I wonder what you are looking for is something like how DBI works (on the inside).

For example when you prepare a statement you get a new copy of an 'Statement' object, or handle to use DBI-ish, while the original statement object stays unchanged from when you start.

Is this what you are looking for??

DBI::st objects are not conceptually immutable. If you call, say fetchrow_array on one, you get back a different row each time. This implies that each call is changing the internal state of the object.

DBI is actually an example of where mutable objects are perfectly sensible: as iterators.

Immutable objects are great because they are easier to reason about, reduce the possibilities for action at a distance, and make testing simpler. But they are not the perfect fit for every problem. Sometimes you need to be prepared to forego the benefits of immutable objects, in favour of coming up with a class design that makes the most sense for the type of data and processes you're modelling.

"DBI::st objects are not conceptually immutable. If you call, say fetchrow_array on one, you get back a different row each time"

Yes of course as you are using an instance of a statement handle. The Original object that was loaded by DBI is unchanged (though I suppose you could changed it as it is only the inner part of a tied hash)

I guess what I was trying to say is that DBI keeps an (hopefully) Immutable copy of the DBI,Driver,and Statement around that it clones from and it would be useful if it could be made immutable

Leave a comment

About mascip

user-pic Perl, Javascript (AngularJS), Coffeescript, Firebase