May 15, 2012
On 14/05/2012 18:08, Tove wrote:
<snip>
> class Outer
> {
>   int i = 6; // mutable
>
>   class Inner {
>     int y=0;
>
>     int foo() const
>     {
>       // ++y; // fail
>       return ++i; // look ma, mutable const
>     }
>   }
>   Inner inner;
>   this()
>   {
>     inner = new Inner;
>   }
>   alias inner this;
> }

Indeed, you've found a hole in the const system nobody seems to have noticed before!

Inner.foo is const, so from foo's point of view, Inner.outer needs to be.

To expand your example a bit:
----------
import std.stdio;

class Outer {
    int i = 6;

    class Inner {
        int y=0;

        int foo() const {
            pragma(msg, "this.outer: " ~ typeof(this.outer).stringof);
            pragma(msg, "i:          " ~ typeof(i).stringof);
            return ++i;
        }
    }
    Inner inner;
    this() {
        inner = new Inner;
    }
}

void main() {
    const(Outer) x = new Outer;
    pragma(msg, "x:          " ~ typeof(x).stringof);
    pragma(msg, "x.inner:    " ~ typeof(x.inner).stringof);
    x.inner.foo();
    writeln(x.i);
}
----------
C:\Users\Stewart\Documents\Programming\D\Tests>dmd inner_const.d
this.outer: const(Outer)
i:          const(int)
x:          const(Outer)
x.inner:    const(Inner)
----------

but nonetheless, it allows i to be modified!

http://d.puremagic.com/issues/show_bug.cgi?id=8098

Stewart.
May 15, 2012
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?
May 15, 2012
Chad J , dans le message (digitalmars.D:167461), a écrit :
> 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:

It is not good for caching results, since the cache often has to be erased when the object is modified.
May 15, 2012
On 05/15/2012 02:49 PM, Christophe wrote:
> Chad J , dans le message (digitalmars.D:167461), a écrit :
>> 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:
>
> It is not good for caching results, since the cache often has to be
> erased when the object is modified.

I did outline a way to invalidate caches with this.
May 15, 2012
On Tuesday, 15 May 2012 at 18:07:12 UTC, Chad J wrote:
> 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:

This is an interesting idea (as it seems to really try to keep
the state changes internal to the function, which can be seen as
how D handles purity)... however, it breaks the point of purity due to this:

    pure nothrow hash_t toHash() const {
        @instance hash_t bad = 0;
        ++bad;
        return hashfn(field) + bad;
    }

Now it violates everyone's definition of purity and we can no
longer make our sweet optimizations and reasoning about the code.
Sure, we could "trust the programmers to not do this" ... but
that's another debate entirely.

> 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;
> 	}
> }

... and setting toStringCacheValid to true in toString violates
const, so this is absolutely not allowed. Sorry.


Maybe there's a solution, but I doubt the solution is something
the programmer can/should do completely transparently.

May 15, 2012
On 05/15/2012 03:32 PM, Chris Cain wrote:
> On Tuesday, 15 May 2012 at 18:07:12 UTC, Chad J wrote:
>> 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:
>
> This is an interesting idea (as it seems to really try to keep
> the state changes internal to the function, which can be seen as
> how D handles purity)... however, it breaks the point of purity due to
> this:
>
> pure nothrow hash_t toHash() const {
> @instance hash_t bad = 0;
> ++bad;
> return hashfn(field) + bad;
> }
>
> Now it violates everyone's definition of purity and we can no
> longer make our sweet optimizations and reasoning about the code.
> Sure, we could "trust the programmers to not do this" ... but
> that's another debate entirely.


Yes.  I intend to "trust the programmers to not do this".

Otherwise we need to find some way to ensure that a function that alters external state will always return the same value as long as the rest of the program doesn't change the state it looks at.

>
>> 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;
>> }
>> }
>
> ... and setting toStringCacheValid to true in toString violates
> const, so this is absolutely not allowed. Sorry.
>

Yep, ya got me.

>
> Maybe there's a solution, but I doubt the solution is something
> the programmer can/should do completely transparently.
>
May 15, 2012
On Tuesday, 15 May 2012 at 21:18:11 UTC, Chad J wrote:
> On 05/15/2012 03:32 PM, Chris Cain wrote:
> Yep, ya got me.
>
>>
>> Maybe there's a solution, but I doubt the solution is something
>> the programmer can/should do completely transparently.

 Perhaps an alternate workaround... a thought coming to mind, is that the constructor for a const object lets you set it once in the constructor (but not touch it again after) So.... Maybe...?

class X {
  uint cached_hash;

  this() {

    cached_hash = X.toHash(this); //last line, only if we can send 'this'
  }

  static uint toHash(const X x) {
    return hashedResult;
  }

  uint toHash(){
    return X.toHash(this);
  }

  uint toHash() const {
    return hash;
  }
}

 Although there's a lot there, with a const and non-const version the const version is optimized and always returned the proper precalculated hash, and if it isn't it creates it on the fly as appropriate.

 You'd almost say an interface you can attach would be the way to get the same basic functionality with minimal effort. So maybe... Perhaps a template is better used. Mmm....

interface CachedHash(T) {
  private uint cachedHash;

  //your hashing function
  static uint toHash(T);

  //call from constructor
  private void setHash() {
    cachedHash = T.toHash(this);
  }
  uint toHash() const {
    return cachedHash;
  }

  override uint toHash() {
    return T.toHash(this);
  }
}

 You should get the basic idea. Same thing would be done for toString with a cached result.
May 15, 2012
On Tuesday, 15 May 2012 at 21:18:11 UTC, Chad J wrote:
> On 05/15/2012 03:32 PM, Chris Cain wrote:
>> On Tuesday, 15 May 2012 at 18:07:12 UTC, Chad J wrote:
>>> 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:
>>
>> This is an interesting idea (as it seems to really try to keep
>> the state changes internal to the function, which can be seen as
>> how D handles purity)... however, it breaks the point of purity due to
>> this:
>>
>> pure nothrow hash_t toHash() const {
>> @instance hash_t bad = 0;
>> ++bad;
>> return hashfn(field) + bad;
>> }
>>
>> Now it violates everyone's definition of purity and we can no
>> longer make our sweet optimizations and reasoning about the code.
>> Sure, we could "trust the programmers to not do this" ... but
>> that's another debate entirely.
>
>
> Yes.  I intend to "trust the programmers to not do this".
>
> Otherwise we need to find some way to ensure that a function that alters external state will always return the same value as long as the rest of the program doesn't change the state it looks at.

It still wouldn't work though. Your @instance variables couldn't
be stored with the object (and, thus, would have to be a pointer
to mutable memory). So it'd require some backend work to make
sure that's even feasible (it is, you could have an immutable
pointer to a mutable pointer to the int, but let's face it:
that's further spitting in the face of the way D's type system
has been designed).

So, you'd have invisible indirections, additional complexity, and
you'd have to trust the programmers to be responsible. In other
words, C++ + the slowness of indirections.

On Tuesday, 15 May 2012 at 22:33:56 UTC, Era Scarecrow wrote:
>  Perhaps an alternate workaround... a thought coming to mind, is that the constructor for a const object lets you set it once in the constructor (but not touch it again after) So.... Maybe...?

This is a good approach, but I think a lot of people want something that's lazy ... if they don't need a hash, they don't want it to be calculated for them.


FWIW, my idea: the thing we really need to do is to R&D some
design patterns & idioms for D. There's solutions for using const
and caching and such, but formalizing them and working out the
kinks to make sure it's optimal for everyone's circumstances
would be helpful. And people will have to accept that C++'s
particular idioms can't be used with D (and vice versa ... some
things you can do easily in D is infeasible or incorrect/invalid
with C++).

Book idea? :) I'd do it, but I'm just a student, so I haven't
seen even a decent subset of all possible software engineering
problems.

May 15, 2012
On 05/15/2012 06:41 PM, Chris Cain wrote:
> On Tuesday, 15 May 2012 at 21:18:11 UTC, Chad J wrote:
>> On 05/15/2012 03:32 PM, Chris Cain wrote:
>>> On Tuesday, 15 May 2012 at 18:07:12 UTC, Chad J wrote:
>>>> 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:
>>>
>>> This is an interesting idea (as it seems to really try to keep
>>> the state changes internal to the function, which can be seen as
>>> how D handles purity)... however, it breaks the point of purity due to
>>> this:
>>>
>>> pure nothrow hash_t toHash() const {
>>> @instance hash_t bad = 0;
>>> ++bad;
>>> return hashfn(field) + bad;
>>> }
>>>
>>> Now it violates everyone's definition of purity and we can no
>>> longer make our sweet optimizations and reasoning about the code.
>>> Sure, we could "trust the programmers to not do this" ... but
>>> that's another debate entirely.
>>
>>
>> Yes. I intend to "trust the programmers to not do this".
>>
>> Otherwise we need to find some way to ensure that a function that
>> alters external state will always return the same value as long as the
>> rest of the program doesn't change the state it looks at.
>
> It still wouldn't work though. Your @instance variables couldn't
> be stored with the object (and, thus, would have to be a pointer
> to mutable memory). So it'd require some backend work to make
> sure that's even feasible (it is, you could have an immutable
> pointer to a mutable pointer to the int, but let's face it:
> that's further spitting in the face of the way D's type system
> has been designed).
>
> So, you'd have invisible indirections, additional complexity, and
> you'd have to trust the programmers to be responsible. In other
> words, C++ + the slowness of indirections.
>

The idea /was/ to store the @instance variable with the object specifically to avoid complex indirections.  Still have to trust programmers though :/

Otherwise it's better to just memoize things using external lookups and whatnot.  That solution does have complex indirections, but it doesn't require trusting the programmer and it exists already.

> On Tuesday, 15 May 2012 at 22:33:56 UTC, Era Scarecrow wrote:
>> Perhaps an alternate workaround... a thought coming to mind, is that
>> the constructor for a const object lets you set it once in the
>> constructor (but not touch it again after) So.... Maybe...?
>
> This is a good approach, but I think a lot of people want something
> that's lazy ... if they don't need a hash, they don't want it to be
> calculated for them.
>
>
> FWIW, my idea: the thing we really need to do is to R&D some
> design patterns & idioms for D. There's solutions for using const
> and caching and such, but formalizing them and working out the
> kinks to make sure it's optimal for everyone's circumstances
> would be helpful. And people will have to accept that C++'s
> particular idioms can't be used with D (and vice versa ... some
> things you can do easily in D is infeasible or incorrect/invalid
> with C++).
>
> Book idea? :) I'd do it, but I'm just a student, so I haven't
> seen even a decent subset of all possible software engineering
> problems.
>

May 16, 2012
On Tuesday, 15 May 2012 at 23:36:38 UTC, Chad J wrote:
> The idea /was/ to store the @instance variable with the object specifically to avoid complex indirections.  Still have to trust programmers though :/

But you /can't/ store the @instance variable with the object. As per the language reference, immutables may be stored in ROM. If the object is immutable (and const objects might be immutable), then the @instance variable would be stored in ROM, in which case it physically couldn't change no matter how hard you tried (or it would vomit run-time errors or some undefined behavior). The only "workable" solution would be an immutable pointer to the mutable variable.

Link: http://dlang.org/const3.html