in reply to The fallacy of the *requirement* for read-only instance variables.

When I use an object, I usually don't care if a method which receives no argument and returns a value is an attribute accessor or not. Neither should I -- that's the beauty of OO, as a user of a class I don't have to care.

Consider Date::Simple, one of my favorite CPAN modules. It has several ways to create an instance of that class - you can either pass in a date in YYYY-MM-DD format, or I can pass in year, month and day, or I can create one as result of an arithmetic operation, or just get it from Date::Simple::today().

On the other I can retrieve several pieces of information from such an object: day, month, year, day of year, day of week and so on. Some of them might be read-only accessors of instance variables, other probably aren't - but I don't have to care.

If one of those methods is an accessor, I'm pretty sure it doesn't meet all three of your criteria for being "legitimate", and yet I'm pretty sure it doesn't indicate any of the four fallacies you listed: it doesn't force me as the caller to perform any extra action, a Date is surely not a catch-all object, it should surely be a class (otherwise arithmetic with it would be very inconvenient), and I can't see a case of premature optimization either.

So why are these methods legitimate, even if you argue that some of them probably aren't, just by because they are ROIAs?

Because they hide implementation details I couldn't care less about. If Date::Simple calculations returned hashes with some data points (say year, month, day), I'd be forced to remember which data is stored in the hash, and which must be calculated from these values (for example day of week). Such a non-uniform interface would put cognitive load on the user - something that should be avoided, even if it means a few extra method calls for obtaining some data.

These accessors also give you a consistent interface when the internal representation changes, thus decoupling API from implementation - another plus.

Now, some will argue: "But what of the caller passes the object to some other piece of code that doesn't have access to the value used during initialisation?". And the answer is, that if the caller can pass the object to that other code, it can equally well pass the value to that other code directly rather than via the object.

And then: "What if the caller has many values that it wants to pass to the other code? Isn't encapsulating them into a data-only object and passing its handle to that other code better than passing a bunch of discrete variables?". And the answer is, how is that better than putting the variables into a simple data structure like a hash or array and passing a reference to that to the other code?

There's also a middle ground: a routine might need to return both some "dumb" data, and an "active" object. If it makes sense for the abstraction in question, it might make sense carrying that "dumb" data in the "active" object, instead of placing the burden on the caller to deal with both of those separately.

For a (public) read-only instance variable (ROIA) to have a value, it must be assigned one. This may happen in one of two ways.

There is a third case: a value might be derived both from values passed by the caller and from "impure" (in the functional programming sense) source like IO and randomness. In this case it might be impossible to obtain the same answer twice, even if the computation isn't expensive. Thus storing data in an attribute might not be just caching, but required for consistency.

Finally if the object needs a value for further calculations, it can just as well make it available for the caller - yet modifying it might invalidate previously calculated data.

Replies are listed 'Best First'.
Re^2: The fallacy of the *requirement* for read-only instance variables.
by BrowserUk (Patriarch) on Apr 17, 2011 at 14:41 UTC
    Some of them might be read-only accessors of instance variables,

    Why would a date class have read-only instance vars?

    However that date object is initialised, why should I not be able to adjust that date by setting one of the values? Why would I not be able to get next year's birthday from this year's by adding 1 to the year component?


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Why would a date class have read-only instance vars?

      Because that way you can use a Date like a value type (that is, share one object in different contexts without the risk that some other context might change it).

      Immutable objects also have the nice property of being sharable among threads without need for any locking.

        Because that way you can use a Date like a value type (that is, share one object in different contexts without the risk that some other context might change it).

        Outside of threading, what type of "contexts" are there which could "share objects" with a risk that one of them might change it without it being a deliberate act on the part of the programmer to do so?

        And if it is a deliberate act, where is the "risk" you speak of?

        Immutable objects also have the nice property of being shareable among threads without need for any locking.

        You are basically saying that every object should be immutable, and therefore every mutation of an object should results in the allocation and population of a new instance of the object and the old one garbage collected, even in single-threaded code, in order that on those odd occasions when you need to pass an existing object to another thread, you don't have to copy it.

        Copy everything every time you shouldn't have to, so that you don't have to copy the one time you should. Way to go.

        And what is the point in sharing a single immutable object anyway? The point of shared data is it allows communication between the sharing threads which is impossible if it is immutable.

        I know that the concurrent pure functional languages use this mechanism. But they also have the benefit of separate compilation and huge, often slow, deep analysing compilers to allow them to avoid actually copying & garbage collecting their "immutable" entities when they can safely mutated in-place. Basically, the costs of immutability are optimised away under the covers by the compiler. The purity is language level concept, not a runtime one. I've previously referred to this as smoke & mirrors.

        But in a dynamic language, with mutability a fundamental and inherent part of the language design--eg. ++$i etal.--this is a nonsense. Show me one concurrent, dynamic language that has efficient garbage collection?

        To make this schema even vaguely efficient, the optimising analysis would have to be done at load-time, and repeated every time the program is loaded. And you'd have to ban eval and all other forms of runtime loading: do, require.

        At which point, why bother with a dynamic language?

        Sorry, but in the context of Perl, I do not believe that you have thought your immutability arguments through.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.