On 05/13/2012 12:39 PM, Stewart Gordon wrote:
http://d.puremagic.com/issues/show_bug.cgi?id=1824

This has gone on for too long.

Object.toString, .toHash, .opCmp and .opEquals should all be const.
(It's also been stated somewhere that they should be pure and nothrow,
or something like that, but I forget where.)

This makes it a nightmare to use const objects in data structures, among
other things, at best forcing the use of ugly workarounds. There are
probably other, more serious effects of this that can't easily be worked
around.

It seems that the main obstacle is rewriting the relevant methods in
std.stream. The current implementation doesn't make sense anyway -
reading the entire contents of a file is certainly not the way to
generate a hash or string representation of the stream. I'm thinking the
hash should probably be the stream handle, and the string representation
could perhaps be the full pathname of the file. Of course, what it
should be for non-file streams is another matter. (This would be a
change at the API level, but when the API's as fundamentally flawed as
this....)

Are there any other bits of druntime/Phobos that need to be sorted out
before these methods can be declared const/pure/nothrow once and for all?

Stewart.

I haven't read all of the replies here, but the gist I'm getting is that we have two contradictory interests: (1.) .toString(), .toHash(), .opCmp(), .opEquals(), should be const/pure/nothrow because their operations are inherently const/pure/nothrow and it would be both unintuitive and less reusable if they weren't. (2.) Marking these as const/pure/nothrow prevents caching and optimizations that are important for real-world code.

When I see a dichotomy like this forming, I have to think that we're missing something. There is definitely a better way! I, for one, wouldn't give up until it's found.



So I'll toss out an idea:

I think the const we want is a kind of "interface const" rather than an "implementation const". Interface const means that calling the method will not cause any /observable/ state-changes in the referred object. Implementation const is stricter: it means that calling the method will not cause ANY state-changes in the referred object at all.

I am going to be fairly strict about my notion of observable. Changes to private members are observable:

class Foo
{
        private string strCache = null;

        // The isCaching method makes strCache "observable".
        public bool isCaching()
        {
                if ( strCache is null )
                        return false;
                else
                        return true;
        }

        public string toString()
        {
                if ( strCache is null )
                {
                        // Observable change in strCache!
                        // ... because isCaching reveals it
                        //   to everyone.
                        strCache = someComplicatedCalculation();
                        return strCache;
                }
                else
                        return strCache;
        }
}


An idea I thought of is to introduce a method local declaration that allows a method to access instance-specific-state that isn't accessible to the rest of the class:

class Foo
{
        // The isCaching method is no longer possible.

        public pure nothrow string toString() const
        {
                // strCache gets stored in an instance of Foo
                // strCache is only accessable in this method body.
                @instance string strCache = null;

                if ( strCache is null )
                {
                        // Observable change in strCache!
                        // ... because isCaching reveals it
                        //   to everyone.
                        strCache = someComplicatedCalculation();
                        return strCache;
                }
                else
                        return strCache;
        }
}

Now it is not possible (or at least, not very easy at all) for the statefulness of strCache to leak into the rest of the class (or program). It is not "observable". If the implementor does their caching wrong, then the statefulness might be observable from the toString method, but nowhere else (except for methods that call toString). It's not a perfect situation, but it's /a lot/ better. We may be required to trust the implementor a little bit and assume that they know how to make sure strCache's statefulness isn't observable (ex: two calls to toString() should return the same results).

To communicate intents to invalidate the cache:

class Foo
{
        private toStringCacheValid = false;

        public void methodThatInvalidatesCache()
        {
                ...
                toStringCacheValid = false;
        }

        public pure nothrow string toString() const
        {
                // strCache gets stored in an instance of Foo
                // strCache is only accessable in this method body.
                @instance string strCache = null;

                if ( !toStringCacheValid )
                {
                        // Observable change in strCache!
                        // ... because isCaching reveals it
                        //   to everyone.
                        strCache = someComplicatedCalculation();
                        toStringCacheValid = true;
                        return strCache;
                }
                else
                        return strCache;
        }
}

This demonstrates how the cache does not need to be touched directly to be invalidated. The information flows from the body of the class and into the cache, and not the other way around.

This may even have implications that interface-constness could be (closely, but not perfectly) maintained by offering write-only declarations in the class body that can only be read by the const method that uses them.

Perhaps something along these lines would satisfy both needs?

Reply via email to