3

The example I'm about to give is for the PHP language, but I think this scenario applies to most languages.

Let's say I have an object called Response and I want it to be immutable. Every method should return a new instance rather than modifying the state of the current instance. Responses have headers, so I create a method called withHeader($name, $value) that makes a copy of my response object, sets the header, and returns the new instance. It's pretty simple to use:

$newResponse = $oldResponse->withHeader('Content-Type', 'application/json');

Then I decide the headers actually have quite a few properties and I'd rather separate this out into a separate object called a HeaderBag. Now I want my Response to hold a HeaderBag but I still want it to be immutable.

I can't make the the bag a public property, like $response->headers because even if the $headers object is immutable, the property can still be reassigned. So maybe I add something like $response->getHeaders() which returns my HeaderBag object, but now how do I manipulate it?

If I move my withHeader method into this sub-object, then I wind up with something like $response->getHeaders()->withHeader(...). The withHeader method would presumably return a new HeaderBag since it too is immutable, but then what? I guess I'd end up with something like this:

$newResponse = $oldResponse->withHeaders($oldResponse->getHeaders()->withHeader($name, $value));

Which seems a bit ridiculous; to add a single value we have to clone two objects and write this abomination of a line. Sure, we can write a helper method on the Response object to shorten it, but the whole idea was to move all the header-related methods into the HeaderBag class.

Questions:

  1. Is there a better way to do this?
  2. If there isn't, is it even worth the effort/cost to make objects immutable?
  3. If we could design a new language with immutability in mind, what would this look like?
5
  • Take a look at PHP's Reflection functionality to see how to update properties without making them public.php.net/manual/en/book.reflection.php. And a look at the _clone() method to simplify cloning objects with nested objects. php.net/manual/en/language.oop5.cloning.php#object.clone. Like many things, it's pretty straight forward to implement with the proper tools. Will PHP's PSR-7 proposal for immutable request/response objects prove useful? Only time will tell. github.com/php-fig/fig-standards/blob/master/proposed/…
    – Cerad
    Apr 19, 2015 at 20:27
  • 2
    @Cerad I'm not sure how reflecton helps here. We can use reflection to access private/protected properties, sure, but what use is that? A better option is __get for read-only access, but there's some overhead in that. clone is how my methods are implemented right now. I'm implementing PSR-7 which is how this came up, but I'm not convinced this immutability is a good idea.
    – mpen
    Apr 19, 2015 at 21:04
  • There is another thread on this topic: programmers.stackexchange.com/questions/278318/… . Reflection can let you set property values without messing around with __set. Here is one implementation: github.com/cerad/http/tree/master/src/HttpMessagePsr7
    – Cerad
    Apr 19, 2015 at 22:02
  • @Cerad That implementation is really weird. Why do you need to use reflection to set protected properties when you are already inside the class? Oh.. it's because you want your method to handle the cloning too, but then you can't access the property from your Util class? Why not make your Util class a trait instead, and drop the reflection? That's unneeded overhead.
    – mpen
    Apr 20, 2015 at 20:36
  • The reflection actually is unnecessary but only because of a PHP implementation detail which allows objects of a given instance type to access the private/protected members of other objects of the same type. I have had some problems with this in the past so tend to avoid that particular 'feature'.
    – Cerad
    Apr 21, 2015 at 13:29

3 Answers 3

7

An http response can naturally be thought of as immutable, but only from the moment that it has been fully constructed and issued. Before that moment, various implementations of "response" classes in various environments are generally used as builders (see Builder Pattern) which get passed around in order for various routines to add to them piecemeal all the information that constitutes the final response. So, during the lifetime of the response as a builder, it is by its very nature mutable.

Now, it is still possible to take a class which is mutable by nature and turn it into an immutable class, but what I would like to suggest to you is that although doing so might be a fabulous exercise on paper, (the entire series of Erik Lippert's articles on immutability was nothing short of fascinating,) it is not really something that you would want to be doing in practice with any non-trivial class, because you end up with abominations precisely like the one you discovered. Plus, it is clearly not worth the effort, and it is stupendously wasteful in terms of computing resources.

So, my suggestion would be that if you really like the idea of an immutable response, (I like it too,) then rename your current response to ResponseBuilder, let it be mutable, and when it is complete, create an immutable response out of it and discard the builder.

3

I will attempt to address the three questions you asked at the very bottom.

1) Is there a better way to do this?

The better way to do this is to build a generic library or language which is capable of efficiently managing the creation and updating of immutable objects. I will try to address this in the answer to the 3rd question.

2) If there isn't, is it even worth the effort/cost to make objects immutable?

This is a hard question, but I would say that unless PHP has a robust library for this (which it may or may not, I am not familiar with PHP) purpose the answer is probably no. First, it would be inefficient and error prone if you just clone your objects. Secondly, if you are working in a language like PHP where the entire language and community is built around and used to mutability, it can be confusing for other programmers when a library is immutable.

3) If we could design a new language with immutability in mind, what would this look like?

Fortunately you wouldn't have to design a new language to know what it would look like because it's already been done. You could look at existing ones like Haskell or Clojure. These languages were designed with immutability in mind. Almost everything in those languages is immutable: Lists, Vectors, Maps, Sets, Records etc and they are implemented in a way which makes updates much faster than copying the entire object.

Clojure for instances uses Hash array mapped trie http://en.wikipedia.org/wiki/Hash_array_mapped_trie to implement its Vectors, Maps, Sets and Records which gives O(log(n)) insert/update speed and the underlying implementation itself is never visible to the end user (in fact small Vectors or Maps may use a completely different representation).

When almost everything in your language is immutable some of the problems you were facing go away. For instance, if the Response holds a Bag there is not such a big problem exposing the Bag because the Bag is immutable too. Also, when you create a new Response where the status code changed, but the headers were the same, you don't need to copy the headers Bag, you can just create a pointer to it because that Bag and everything contained in it is immutable! Allowing the compiler, runtime and libraries to make assumptions about the immutability of other things in the language is part of what allows languages designed with immutable data structures to be more efficient and more usable than trying add immutability as an afterthought to a language which started off mutable.

This is actually how the popular Ring framework https://github.com/ring-clojure/ring works in Clojure. A response is just a Map:

{:status 200
  :headers {"Content-Type" "text/html"}
  :body "Hello World"}

As you can see, the response is a Map, and the headers are just another Map. What it would look like to produce a new Map with Content-Type="text/xml" would be:

(assoc-in response [:headers "Content-Type"] "text/xml")

The assoc-in function is from the standard library which does the work from the "ridiculous" example of knowing how to get at the nested Map and rebuild the data structures it was a part of so that the programmer doesn't need to make the explicit calls to your withHeader and withHeaders functions. There are also numerous other techniques and functions which make common operations easier. For instance if we had more than one update we wanted to make to the response we might use the -> or thread-first macro.

(-> response
   (assoc-in [:headers "Content-Type"] "text/xml")
   (assoc-in [:headers "Content-Disposition"] "attachment; filename=\"data.xml\""))

There are also many methods in the standard libraries for these languages which could apply a function to a value in a nested structure, merge two immutable Maps together, compare Maps for equality, etc.

2
  • "if you are working in a language like PHP where the entire language and community is built around and used to mutability, it can be confusing for other programmers when a library is immutable." This is for a PSR standard proposal; I agree it will probably be confusing to users, but I guess they'd better get used to it if this goes forward. "Also, when you create a new Response where the status code changed, but the headers were the same, you don't need to copy the headers Bag, you can just create a pointer to it because that Bag and everything contained in it is immutable!" --
    – mpen
    Apr 19, 2015 at 21:07
  • -- If HeaderBag is implemented correctly, we can just do a shallow copy, we wouldn't be cloning the whole thing, even in PHP. But still, it can be a lot of little properties to copy around. I'm not sure if your Clojure example is a fair comparison. You've exposed :headers as a map -- I'm saying what if it was an object? In PSR7 headers can be accessed via a case-insensitive key, but the original header name must be retained, and they can contain one or more values, so they're a little more complicated than just a map.
    – mpen
    Apr 19, 2015 at 21:11
1

Disclaimer: I wrote this as I was coming up with it

I think ideally we would have read-only properties, like in C#. Then we could expose $response->headers without worrying it'll be re-assigned. Then what we need is a way for HeaderBag::withHeader to return a (new) Response object, but only when we've accessed it via the response object -- not if we're working with it on its own. i.e. this would return a new Response object,

$newResponse = $oldResponse->headers->withHeader('Content-Type', 'text/xml')

but this would return a HeaderBag object:

$newHeaders = $standaloneHeaders->withHeader('Content-Type', 'text/xml');

This of course would be super confusing if the method returned different objects depending on how it was called, so we need fix up the syntax a bit to make it more clear...

What if we had a new "with" operator (ω) that behaves a bit like assignment (=) except it returns a new instance of the object on the left with the new property set. For example, instead of

obj.x = 5

You would write

obj2 = obj ω x = 5

Better yet, you could manipulate several properties at once while only making one copy of the object:

obj2 = obj ω x = 5, y = 6

Then going back to our example above we could write:

$newResponse = $oldResponse ω headers = $oldResponse->headers->withHeader('Content-Type', 'text/xml');

But that's not much better. Maybe we can add another symbol to represent the object being manipulated? For example, if you wanted to increment a value, you could either write:

obj2 = obj ω x = obj.x + 1

Or

obj2 = obj ω x = τ + 1

Where τ refers to the property just after the ω. Now we can write:

$newResponse = $oldResponse ω headers = τ->withHeader('Content-Type', 'text/xml');

Getting better, but I think we can still improve it. Let's pretend our HeaderBag implements an array interface, such that we can get a header via $response->headers['Content-Type'].

$newResponse = $oldResponse ω headers = τ ω τ['Content-Type'] = 'text/xml';

Now let's see what it looks like to set a whole bunch of properties all at once:

$newResponse = $oldResponse 
    ω headers = τ ω τ['Content-Type'] = 'text/xml', 
                    τ['Content-Disposition'] = 'attachment; filename="data.xml"',
      status = 200,
      body = '<root>text</root>'

I think I might need some parentheses in there, or maybe I should remove the , operator and require more ωs, I'm not sure, but that's the general idea.

We might want to replace ω with something a bit easier to type, perhaps w/? For τ I was thinking "this" but that has a different meaning inside of classes.

This needs a bit more thought, but I welcome your feedback. WuHoUnited gave some examples of manipulating nested objects in Clojure, but I'm not sure if it's as flexible as my solution which lets you call any method at any point in the chain.


With this semi-ideal solution in mind, we can try converting it back into syntactically valid PHP:

$newResponse = $oldResponse->withProp(['headers' => function($headers) {
    return $headers->withKey(['Content-Type' => 'application/json']);
]);

Something like that. Implementation would look like:

<?php
trait WithTrait {

    public function withProp($data) {
        $new = clone $this;

        foreach($data as $prop=>$cb) {
            $new->{$prop} = $cb($this->{$prop});
        }

        return $new;
    }

    public function withKey($data) {
        $new = clone $this;

        foreach($data as $key=>$cb) {
            $new[$key] = $cb($this[$key]);
        }

        return $new;
    }
}

I think this could work, but I don't think I want to go down this road when Mike's solution is so much simpler.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.