www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Getting the const-correctness of Object sorted once and for all

reply Stewart Gordon <smjg_1998 yahoo.com> writes:
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.
May 13 2012
next sibling parent reply =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 13-05-2012 18:39, 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 agree with everything but toString(). I'm afraid that forcing toString() to be const will have harm flexibility severely. Can't we do better, somehow? -- - Alex
May 13 2012
next sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
On 13/05/2012 17:41, Alex Rønne Petersen wrote:
<snip>
 I agree with everything but toString(). I'm afraid that forcing toString() to
be const
 will have harm flexibility severely. Can't we do better, somehow?

How exactly? If you're talking about memoization, it ought to be possible to make use of std.functional.memoize to implement it. Otherwise, using toString to change the state of an object is bending semantics. If you want a method to generate a string representation of an object in a way that might do this, then create your own method to do it. Stewart.
May 13 2012
parent reply =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 13-05-2012 19:02, Stewart Gordon wrote:
 On 13/05/2012 17:41, Alex Rønne Petersen wrote:
 <snip>
 I agree with everything but toString(). I'm afraid that forcing
 toString() to be const
 will have harm flexibility severely. Can't we do better, somehow?

How exactly? If you're talking about memoization, it ought to be possible to make use of std.functional.memoize to implement it. Otherwise, using toString to change the state of an object is bending semantics. If you want a method to generate a string representation of an object in a way that might do this, then create your own method to do it. Stewart.

But how would you memoize the value in the instance of the object if it's const? -- - Alex
May 13 2012
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
On 13/05/2012 18:04, Alex Rønne Petersen wrote:
<snip>
 But how would you memoize the value in the instance of the object if it's
const?

By storing the memoized value outside of the object. Read the code of std.functional.memoize - you'll see that it holds the values in a static AA. Stewart.
May 13 2012
parent reply =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 13-05-2012 19:27, Stewart Gordon wrote:
 On 13/05/2012 18:04, Alex Rønne Petersen wrote:
 <snip>
 But how would you memoize the value in the instance of the object if
 it's const?

By storing the memoized value outside of the object. Read the code of std.functional.memoize - you'll see that it holds the values in a static AA. Stewart.

Which forces you to: 1) use an AA lookup 2) use the GC Not an optimal solution IMHO. -- - Alex
May 13 2012
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
On 13/05/2012 18:53, Alex Rønne Petersen wrote:
<snip>
 Which forces you to:

 1) use an AA lookup
 2) use the GC

 Not an optimal solution IMHO.

But you're going to need to use the GC anyway. How else are you going to store the string? Memoization is a form of laziness. This concept applies to the allocation of memory to hold the result as much as to the actual calculation of the result. Stewart.
May 13 2012
parent =?ISO-8859-1?Q?Alex_R=F8nne_Petersen?= <xtzgzorex gmail.com> writes:
On 13-05-2012 21:11, Stewart Gordon wrote:
 On 13/05/2012 18:53, Alex Rønne Petersen wrote:
 <snip>
 Which forces you to:

 1) use an AA lookup
 2) use the GC

 Not an optimal solution IMHO.

But you're going to need to use the GC anyway. How else are you going to store the string?

malloc? (And free it in the destructor or whatever - depends on your manual memory management approach.)
 Memoization is a form of laziness. This concept applies to the
 allocation of memory to hold the result as much as to the actual
 calculation of the result.

 Stewart.

-- - Alex
May 13 2012
prev sibling next sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Sunday, 13 May 2012 at 17:02:46 UTC, Stewart Gordon wrote:
 On 13/05/2012 17:41, Alex Rønne Petersen wrote:
 <snip>
 I agree with everything but toString(). I'm afraid that 
 forcing toString() to be const
 will have harm flexibility severely. Can't we do better, 
 somehow?

How exactly? If you're talking about memoization, it ought to be possible to make use of std.functional.memoize to implement it. Otherwise, using toString to change the state of an object is bending semantics. If you want a method to generate a string representation of an object in a way that might do this, then create your own method to do it. Stewart.

How about logically constant opEquals, toString etc? Currently, this is perfectly possible by just *not using const*. Logical constancy goes beyond memoization. For example, take this function: http://jakobovrum.github.com/LuaD/luad.base.html#LuaObject.opEquals It cannot be const because Lua instances are state machines. The function pushes the this-object to the Lua stack, then it pushes the object to compare to, then it uses a Lua API comparison function which pops the two objects and pushes the comparison result. The result is then popped off the stack and returned as a boolean. The function is logically constant, hence it does not use D's const, which should be a completely valid choice to make.
May 13 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 14 May 2012 02:35:16 -0400, Jakob Ovrum <jakobovrum gmail.com>  =

wrote:

 On Sunday, 13 May 2012 at 17:02:46 UTC, Stewart Gordon wrote:
 On 13/05/2012 17:41, Alex R=C3=B8nne Petersen wrote:
 <snip>
 I agree with everything but toString(). I'm afraid that forcing  =



 toString() to be const
 will have harm flexibility severely. Can't we do better, somehow?

How exactly? If you're talking about memoization, it ought to be possible to make =


 use of std.functional.memoize to implement it.

 Otherwise, using toString to change the state of an object is bending=


 semantics.  If you want a method to generate a string representation =


 an object in a way that might do this, then create your own method to=


 do it.

 Stewart.

How about logically constant opEquals, toString etc? Currently, this i=

 perfectly possible by just *not using const*. Logical constancy goes  =

 beyond memoization.

This means you cannot compare two const objects. The issue is, non-const opEquals makes sense on some objects, and const = = opEquals makes sense on others. However, you must make them all come = together in Object.opEquals. I think we already have the hooks to properly compare objects without = requiring Object.opEquals. Right now, when two objects are compared, the compiler calls = object.opEquals (that's little o for object, meaning the module function= = *not* the class method). So why can't object.opEquals be a template that decides whether to use = Object.opEquals (which IMO *must be* const) or a derived version? I don= 't = think it's that much of a stretch (not sure if we need a compiler fix fo= r = this). -Steve
May 14 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Alex Rønne Petersen:

 But how would you memoize the value in the instance of the 
 object if it's const?

I opened a thread on such matters: http://forum.dlang.org/thread/gtpdmrfektaygfmecupj forum.dlang.org ------------------- Jonathan M Davis:
Caching and lazy evaluation _will_ be impossible in those 
functions without breaking the type system.<

Take a look at the articles I've linked. Bye, bearophile
May 14 2012
prev sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Monday, 14 May 2012 at 12:22:30 UTC, Steven Schveighoffer
wrote:
 On Mon, 14 May 2012 02:35:16 -0400, Jakob Ovrum 
 <jakobovrum gmail.com> wrote:
 How about logically constant opEquals, toString etc? 
 Currently, this is perfectly possible by just *not using 
 const*. Logical constancy goes beyond memoization.

This means you cannot compare two const objects.

Yes, but using const for these objects makes no sense because they all require temporary mutation (the Lua stack) to do anything meaningful. This includes opEquals and toString. Thus the option should be there not to use const.
 The issue is, non-const opEquals makes sense on some objects, 
 and const opEquals makes sense on others.  However, you must 
 make them all come together in Object.opEquals.

 I think we already have the hooks to properly compare objects 
 without requiring Object.opEquals.

 Right now, when two objects are compared, the compiler calls 
 object.opEquals (that's little o for object, meaning the module 
 function *not* the class method).

 So why can't object.opEquals be a template that decides whether 
 to use Object.opEquals (which IMO *must be* const) or a derived 
 version?  I don't think it's that much of a stretch (not sure 
 if we need a compiler fix for this).

 -Steve

Right, I think this is the way to go. We have to accommodate both kind of object.
May 14 2012
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 5/13/2012 9:39 AM, Stewart Gordon wrote:
 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....)

I'd like to see std.stream dumped. I don't see any reason for it to exist that std.stdio doesn't do (or should do). The only reason it's there at the moment is for backwards compatibility.
May 13 2012
next sibling parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Sunday, 13 May 2012 at 19:43:19 UTC, Walter Bright wrote:
 On 5/13/2012 9:39 AM, Stewart Gordon wrote:
 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....)

I'd like to see std.stream dumped. I don't see any reason for it to exist that std.stdio doesn't do (or should do). The only reason it's there at the moment is for backwards compatibility.

Recently in my own code I've started making use of std.stream, mostly creating temporary string streams to test IO in functions that would otherwise need actual files for unittests. Glancing over std.stdio, I only see specific file access functions and routines. If I'm doing the wrong approach please correct me now. --- import std.stream; struct Header { char[4] name; int size; int read(InputStream is_in) { is_in.readExact(name.ptr, name.sizeof); is_in.read(size); return this.sizeof; } int write(OutputStream os_out) { os_out.writeExact(name.ptr, name.sizeof); os_out.write(size); return this.sizeof; } unittest { ubyte[Header.sizeof] buffer; auto buff_stream = new TArrayStream!(ubyte[])(buffer); Header x = Header("abcd", 1024); x.write(buff_stream); assert(buffer == [97, 98, 99, 100, 0, 4, 0, 0]); buff_stream.seekSet(0); x = Header(); assert(x.size == 0); x.read(buff_stream); assert(x.name == "abcd"); assert(x.size == 1024); } }
May 13 2012
parent Walter Bright <newshound2 digitalmars.com> writes:
On 5/13/2012 12:57 PM, Era Scarecrow wrote:
 On Sunday, 13 May 2012 at 19:43:19 UTC, Walter Bright wrote:
 On 5/13/2012 9:39 AM, Stewart Gordon wrote:
 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....)

I'd like to see std.stream dumped. I don't see any reason for it to exist that std.stdio doesn't do (or should do). The only reason it's there at the moment is for backwards compatibility.

Recently in my own code I've started making use of std.stream, mostly creating temporary string streams to test IO in functions that would otherwise need actual files for unittests.

I suggest switching over to a range interface. Then, your code need not care if it is getting input from files, arrays, or any containers. Same for output. That means you can use arrays to unittest it, or write your own mock input/output ranges.
 Glancing over std.stdio, I only see specific file access functions and
routines.
 If I'm doing the wrong approach please correct me now.

std.stdio's support for ranges needs some improvement.
May 13 2012
prev sibling next sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
On 13/05/2012 20:42, Walter Bright wrote:
<snip>
 I'd like to see std.stream dumped. I don't see any reason for it to exist that
std.stdio
 doesn't do (or should do).

So std.stdio.File is the replacement for the std.stream stuff? How does/will it provide all the different kinds of stream that std.stream provides, as well as the other kinds of stream that applications will need? What's the plan for std.cstream?
 The only reason it's there at the moment is for backwards compatibility.

So the plan is to deprecate that, then remove it, _then_ fix this issue? Or make toString and toHash in the stream classes bypass constancy? Stewart.
May 13 2012
next sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 14.05.2012 0:48, Stewart Gordon wrote:
 On 13/05/2012 20:42, Walter Bright wrote:
 <snip>
 I'd like to see std.stream dumped. I don't see any reason for it to
 exist that std.stdio
 doesn't do (or should do).

So std.stdio.File is the replacement for the std.stream stuff? How does/will it provide all the different kinds of stream that std.stream provides, as well as the other kinds of stream that applications will need?

I think I've seen proper replacement (or rather a draft of it). If only Steven can be bothered to finish it :)
 What's the plan for std.cstream?

 The only reason it's there at the moment is for backwards compatibility.

So the plan is to deprecate that, then remove it, _then_ fix this issue? Or make toString and toHash in the stream classes bypass constancy? Stewart.

-- Dmitry Olshansky
May 13 2012
parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 14.05.2012 16:37, Steven Schveighoffer wrote:
 On Sun, 13 May 2012 16:52:15 -0400, Dmitry Olshansky
 <dmitry.olsh gmail.com> wrote:

 On 14.05.2012 0:48, Stewart Gordon wrote:
 On 13/05/2012 20:42, Walter Bright wrote:
 <snip>
 I'd like to see std.stream dumped. I don't see any reason for it to
 exist that std.stdio
 doesn't do (or should do).

So std.stdio.File is the replacement for the std.stream stuff? How does/will it provide all the different kinds of stream that std.stream provides, as well as the other kinds of stream that applications will need?

I think I've seen proper replacement (or rather a draft of it). If only Steven can be bothered to finish it :)

Yes, I know. I hate starting things and then not finishing them. Especially when I've got so much of it completed... I'm going to make some time to finish this. I need probably a good few days (of solid time). Which means, based on my normal schedule, 2-3 weeks. Most of the difficult parts are complete, I have a working buffer implementation, fast unicode translation (meaning UTF-8, UTF-16, UTF-16LE, UTF-32, and UTF-32LE), and a path to use RAII for seamless integration with std.stdio.File. I even have preliminary agreement from Andrei and Walter on a design (no guarantees they accept the final product, but I think I can massage it into acceptance).

Great!
 The one last puzzle to solve is sharing. File is this half-breed of
 sharing, because it contains a FILE *, which is a shared type, but File
 is not. Then it does some casting to get around the problems.

And it all went nice and well till I spotted FILE*. Boom. I mean I half assumed we are going to drop this crutch. Yep, too harsh. So probably leave it as *one* of file backends that features compatibility with C. Because IMHO using C-runtime this way is not buying us anything else other then C compatibility and "welcome to the world of unportable hacks where performance matters". We need a
 better solution than this, but shared is so difficult to use, I think
 I'm going to have to implement something similar. It has been stipulated
 by Walter and Andrei that fixing this shared situation is a requirement
 for any new replacement. I have some ideas, but I have to play around to
 see if they actually work and make sense.

-- Dmitry Olshansky
May 14 2012
parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 14.05.2012 23:26, Steven Schveighoffer wrote:
 On Mon, 14 May 2012 15:23:59 -0400, Steven Schveighoffer
 <schveiguy yahoo.com> wrote:

 On Mon, 14 May 2012 15:07:30 -0400, Andrej Mitrovic
 <andrej.mitrovich gmail.com> wrote:

 On 5/14/12, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 Really, printf is the *only* reason to have this backwards
 compatibility
 "feature", and I strongly wish we could get rid of it.

printf is also unique in that it works when called in class destructors, which is sometimes needed for debugging (unlike writef which wants to allocate memory and then throws).

That's an excellent point. But what a really mean is, I wish we could get rid of the requirement for interoperability between printf and writef. Of course, we couldn't get rid of printf, it's part of the C runtime!

Oh, and also, we should fix that problem (that writef allocates). However, I think we need DIP9 in order to do that. http://prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP9

DIP9 for the win! I tried to revive at least *some* interest in it for a long time. -- Dmitry Olshansky
May 14 2012
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 5/13/2012 1:48 PM, Stewart Gordon wrote:
 On 13/05/2012 20:42, Walter Bright wrote:
 <snip>
 I'd like to see std.stream dumped. I don't see any reason for it to exist that
 std.stdio
 doesn't do (or should do).

So std.stdio.File is the replacement for the std.stream stuff?

Not exactly. Ranges are the replacement. std.stdio.File is merely a range that deals with files.
 How does/will it provide all the different kinds of stream that std.stream
 provides, as well as the other kinds of stream that applications will need?

The future is a range interface, not a stream one.
 What's the plan for std.cstream?

I don't see a purpose for std.cstream.
 The only reason it's there at the moment is for backwards compatibility.

So the plan is to deprecate that, then remove it, _then_ fix this issue? Or make toString and toHash in the stream classes bypass constancy?

What do you suggest?
May 13 2012
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, May 14, 2012 at 11:55:15PM +0400, Dmitry Olshansky wrote:
 On 14.05.2012 23:26, Steven Schveighoffer wrote:

Oh, and also, we should fix that problem (that writef allocates).
However, I think we need DIP9 in order to do that.
http://prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP9

DIP9 for the win! I tried to revive at least *some* interest in it for a long time.

+1. I like this proposal. I have never liked the idea of toString() ever since Java introduced it. Besides memory usage issues, toString() also binds you to the string type for no good reason: what if you wanted to store the result into a database file? Why waste resources on an intermediate representation if it can be written straight to its final destination? I'd even argue that DIP9 is an excellent example of: http://en.wikipedia.org/wiki/Dependency_inversion_principle We don't know what representation the caller ultimately wants (string in memory, file on disk, network socket, etc.), so why impose an essentially arbitrary concrete representation type? Let the delegate decide what to do with the data. T -- Just because you survived after you did it, doesn't mean it wasn't stupid!
May 14 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 13 May 2012 16:52:15 -0400, Dmitry Olshansky  
<dmitry.olsh gmail.com> wrote:

 On 14.05.2012 0:48, Stewart Gordon wrote:
 On 13/05/2012 20:42, Walter Bright wrote:
 <snip>
 I'd like to see std.stream dumped. I don't see any reason for it to
 exist that std.stdio
 doesn't do (or should do).

So std.stdio.File is the replacement for the std.stream stuff? How does/will it provide all the different kinds of stream that std.stream provides, as well as the other kinds of stream that applications will need?

I think I've seen proper replacement (or rather a draft of it). If only Steven can be bothered to finish it :)

Yes, I know. I hate starting things and then not finishing them. Especially when I've got so much of it completed... I'm going to make some time to finish this. I need probably a good few days (of solid time). Which means, based on my normal schedule, 2-3 weeks. Most of the difficult parts are complete, I have a working buffer implementation, fast unicode translation (meaning UTF-8, UTF-16, UTF-16LE, UTF-32, and UTF-32LE), and a path to use RAII for seamless integration with std.stdio.File. I even have preliminary agreement from Andrei and Walter on a design (no guarantees they accept the final product, but I think I can massage it into acceptance). The one last puzzle to solve is sharing. File is this half-breed of sharing, because it contains a FILE *, which is a shared type, but File is not. Then it does some casting to get around the problems. We need a better solution than this, but shared is so difficult to use, I think I'm going to have to implement something similar. It has been stipulated by Walter and Andrei that fixing this shared situation is a requirement for any new replacement. I have some ideas, but I have to play around to see if they actually work and make sense. -Steve
May 14 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 14 May 2012 09:20:57 -0400, Dmitry Olshansky  
<dmitry.olsh gmail.com> wrote:

 On 14.05.2012 16:37, Steven Schveighoffer wrote:

 The one last puzzle to solve is sharing. File is this half-breed of
 sharing, because it contains a FILE *, which is a shared type, but File
 is not. Then it does some casting to get around the problems.

And it all went nice and well till I spotted FILE*. Boom. I mean I half assumed we are going to drop this crutch. Yep, too harsh. So probably leave it as *one* of file backends that features compatibility with C. Because IMHO using C-runtime this way is not buying us anything else other then C compatibility and "welcome to the world of unportable hacks where performance matters".

I think the compromise agreed upon will be reasonable. A constructed File will start out as a FILE * entity until you want to do anything more complex than writeln or readf. At that point, it switches over to D-based backend automatically. I don't want to be trying to implement advanced buffer-based techniques using FILE *, and I also don't want to restrict phobos' types to only doing things that FILE * can do well. At the same time, we have this legacy with std.stdio.File that we have to maintain (the comments on my preliminary library were near-unanimous -- it cannot break existing code). Walter has a very very hard requirement that D's equivalent stdout stdin, and stderr all interoperate with the C calls that use the equivalent C structures. In other words, it is a hard requirement that writeln and printf interoperate. Really, printf is the *only* reason to have this backwards compatibility "feature", and I strongly wish we could get rid of it. That being said, if you *want* to avoid FILE *, it will definitely be possible.
 We need a
 better solution than this, but shared is so difficult to use, I think
 I'm going to have to implement something similar. It has been stipulated
 by Walter and Andrei that fixing this shared situation is a requirement
 for any new replacement. I have some ideas, but I have to play around to
 see if they actually work and make sense.


I have. I don't know how well it applies to my library. One thing about using FILE * is that you have full knowledge of the known universe of FILE *. This means you can control internally the interactions between threads, making sure that you don't have an unshared pointer unless the thing is locked (and this is what std.stdio does). The problem is when you create an *extendable* system like the one I'm doing. At that point, you can lock when you cast away shared, but you have no idea whether a method call is going to squirrel away an unshared reference to itself somewhere, so that when you go back to shared (and unlock), there's a leaked thread-local reference somewhere. It may have to require documentation-based restrictions (i.e. not-compiler-enforced). I haven't put enough thought into the possible means to do this. Ideas are welcome! -Steve
May 14 2012
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 5/14/12, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 Really, printf is the *only* reason to have this backwards compatibility
 "feature", and I strongly wish we could get rid of it.

printf is also unique in that it works when called in class destructors, which is sometimes needed for debugging (unlike writef which wants to allocate memory and then throws).
May 14 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 14 May 2012 15:07:30 -0400, Andrej Mitrovic  
<andrej.mitrovich gmail.com> wrote:

 On 5/14/12, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 Really, printf is the *only* reason to have this backwards compatibility
 "feature", and I strongly wish we could get rid of it.

printf is also unique in that it works when called in class destructors, which is sometimes needed for debugging (unlike writef which wants to allocate memory and then throws).

That's an excellent point. But what a really mean is, I wish we could get rid of the requirement for interoperability between printf and writef. Of course, we couldn't get rid of printf, it's part of the C runtime! -Steve
May 14 2012
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 14 May 2012 15:23:59 -0400, Steven Schveighoffer
<schveiguy yahoo.com> wrote:

 On Mon, 14 May 2012 15:07:30 -0400, Andrej Mitrovic  
 <andrej.mitrovich gmail.com> wrote:

 On 5/14/12, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 Really, printf is the *only* reason to have this backwards  
 compatibility
 "feature", and I strongly wish we could get rid of it.

printf is also unique in that it works when called in class destructors, which is sometimes needed for debugging (unlike writef which wants to allocate memory and then throws).

That's an excellent point. But what a really mean is, I wish we could get rid of the requirement for interoperability between printf and writef. Of course, we couldn't get rid of printf, it's part of the C runtime!

Oh, and also, we should fix that problem (that writef allocates). However, I think we need DIP9 in order to do that. http://prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs/DIP9 -Steve
May 14 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, May 13, 2012 17:39:44 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.

toHash, opCmp, opEquals, and toString will _all_ be required to be const safe pure nothrow. Some work has been done towards that, but it hasn't been completed. One of the major obstacles is the inability for stuff such as format, to!string, or Appender to be pure right now. A number of druntime functions are going to need to be marked as pure for that to happen (particilurly reserve and capacity, but there are probably others). Some effort has been put in that area recently, but it hasn't been completed. - Jonathan M Davis
May 13 2012
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/13/2012 06: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.

No, please. Transitive enforced const + OO -> epic fail. (especially for unshared data, which is the default and most common) The lack of head-mutable class references does not make this any better. I'd rather keep the current state of affairs than blindly mark the existing methods const. Caching/lazy evaluation is everywhere. One single lazily cached class member implies that all of the relevant methods cannot use that member. What if they have to? They certainly have to! Const/immutable are great for simple value-types. Classes can contain immutable values as fields.
 (It's also been stated somewhere that they should be pure and nothrow,
 or something like that, but I forget where.)

Similar concerns apply for them (but less so than for const). I don't want to have to violate the type system to get basic stuff done in OO-ish style! Forced const invariants are annoying enough already.
 This makes it a nightmare to use const objects in data structures,

Why on earth would you want to do that?
 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.

I wouldn't bet on that. Marking the methods const, on the other hand, certainly would have serious effects.
 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?

Almost _all_ of Phobos would have to become const "correct", and it cannot, because 'const' is an over-approximation and there is no const-inference. const on the top-class interface invades _everything_. Marking the methods const would break every non-trivial D program out there and put programmers in a frustrating straitjacket.
May 13 2012
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, May 14, 2012 00:30:09 Timon Gehr wrote:
 On 05/13/2012 06: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.

No, please. Transitive enforced const + OO -> epic fail. (especially for unshared data, which is the default and most common) The lack of head-mutable class references does not make this any better. I'd rather keep the current state of affairs than blindly mark the existing methods const. Caching/lazy evaluation is everywhere. One single lazily cached class member implies that all of the relevant methods cannot use that member. What if they have to? They certainly have to! Const/immutable are great for simple value-types. Classes can contain immutable values as fields.

Walter fully intends to require that opEquals, opCmp, toHash, and toString all be const safe pure nothrow - on both classes and structs. And the reality of the matter is, if that requirement _isn't_ there on classes, then the usage of any of those functions in safe, const, pure, or nothrow code is seriously hampered - especially const and pure. This has been discussed quite a bit before, and we're pretty much stuck thanks to transivity. Caching and lazy evaluation _will_ be impossible in those functions without breaking the type system. Anything that absolutely requires them will probably have to either have to break the type system or use _other_ functions with the same functionality but without those attributes. In some cases though, providing overloads which aren't const, pure, etc. should work though. If you want it to be otherwise, you're going to have to convince Walter, and I think that it's pretty clear that this is the way that it's going to have to be thanks to how const et al. work. - Jonathan M Davis
May 13 2012
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 5/13/2012 11:09 PM, Mehrdad wrote:
 On Monday, 14 May 2012 at 04:10:30 UTC, Chris Cain wrote:
 Let me ask you something: Could you try to name 2 or 3 good things about
 const/immutable without looking it up? If not, you've really not given enough
 effort to learning about the benefits of it.

(1) Compiler helps you write correct multithreaded code (2) You help compiler perform optimizations based on contracts (3) I don't think there exists a #3 that's very different from #1 and #2

#3 Improves self-documentation of code - it's more understandable and less susceptible to breakage during maintenance. #4 Improves encapsulation #5 Makes function purity possible
May 13 2012
next sibling parent =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <xtzgzorex gmail.com> writes:
On 14-05-2012 08:26, Walter Bright wrote:
 On 5/13/2012 11:09 PM, Mehrdad wrote:
 On Monday, 14 May 2012 at 04:10:30 UTC, Chris Cain wrote:
 Let me ask you something: Could you try to name 2 or 3 good things about
 const/immutable without looking it up? If not, you've really not
 given enough
 effort to learning about the benefits of it.

(1) Compiler helps you write correct multithreaded code (2) You help compiler perform optimizations based on contracts (3) I don't think there exists a #3 that's very different from #1 and #2

#3 Improves self-documentation of code - it's more understandable and less susceptible to breakage during maintenance. #4 Improves encapsulation

I'm not sure I would agree on this one. const/immutable arguably leak implementation details more than they encapsulate them.
 #5 Makes function purity possible

-- - Alex
May 13 2012
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 5/14/2012 12:47 AM, Chris Cain wrote:
 And all this together culminates into a much greater ability to _reason about
 code_.

Exactly. And ditto for the rest of your post - it's spot on. Being able to reason about code is *intensely* important. All those decorations - const, pure, nothrow, out - serve to help that out.
May 14 2012
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 5/14/2012 1:03 AM, Mehrdad wrote:
 On Monday, 14 May 2012 at 06:27:15 UTC, Walter Bright wrote:
 #3 Improves self-documentation of code - it's more understandable and less
 susceptible to breakage during maintenance.

#3 is also valid for C++, so I wasn't exactly considering that.

No, it isn't. 1. C++ legally allows changing const values. 2. const only applies to the top level (i.e. head const). So if you have anything more than a simple value type, it literally means *nothing*.
 #4 Improves encapsulation

o.O How does it improve encapsulation?

By guaranteeing that the method call is a reader, not a mutator. D is a very deliberate step away from correctness by convention and towards correctness with mechanical verification.
May 15 2012
prev sibling next sibling parent Stewart Gordon <smjg_1998 yahoo.com> writes:
On 13/05/2012 23:50, Jonathan M Davis wrote:
<snip>
 Caching and lazy
 evaluation _will_ be impossible in those functions without breaking the type
 system.

Unless stuff is added to the type system to accommodate it. For example, a type modifier that adds a "set" flag. When unset, a const method cannot read it (to do so would throw an AssertError or similar), but can assign to it, at which point it becomes set. When set, it acts just like any member (non-const methods have read-write access, const methods have read-only access, etc.). Non-const methods can also unset it, which they would do if they change the state of the object in a way that invalidates the cached value. Alternatively, you could argue that it's the compiler's job to implement caching as an optimisation for pure methods. But it would have to implement the logic to decache it when relevant state of the object changes, which could get complicated if you want to do it efficiently.
 Anything that absolutely requires them will probably have to either
 have to break the type system or use _other_ functions with the same
 functionality but without those attributes.

I think that's half the point of std.functional.memoize - to be such a function for you to use when you want it. Stewart.
May 14 2012
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/14/2012 06:10 AM, Chris Cain wrote:
 On Monday, 14 May 2012 at 02:57:57 UTC, Mehrdad wrote:
 The problem is that it's unavoidable.

 i.e. you can't say "don't mark it as const if it isn't const",
 because, practically speaking, it's being forced onto the programmers
 by the language.

You're really against const in this language, huh?

I guess this is not the most important point. He has been trying to use const like in OO-ish C++. This just does not work, because D const is detrimental to OO principles when used that way. The proposal is about _enforcing_ C++-like usage of const.
May 14 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 14/05/2012 21:24, Steven Schveighoffer a écrit :
 On Mon, 14 May 2012 13:08:06 -0400, Tove <tove fransson.se> wrote:

 On Monday, 14 May 2012 at 16:53:24 UTC, Timon Gehr wrote:
 On 05/14/2012 06:10 AM, Chris Cain wrote:
 On Monday, 14 May 2012 at 02:57:57 UTC, Mehrdad wrote:
 The problem is that it's unavoidable.

 i.e. you can't say "don't mark it as const if it isn't const",
 because, practically speaking, it's being forced onto the programmers
 by the language.

You're really against const in this language, huh?

I guess this is not the most important point. He has been trying to use const like in OO-ish C++. This just does not work, because D const is detrimental to OO principles when used that way. The proposal is about _enforcing_ C++-like usage of const.

but c++ has the 'mutable' keyword as an easy escape route... which saved me a bunch of times... guess one can emulate it with a library-solution using nested classes? But... what about structs? 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; }

I have never seen this suggested before. I would guess that it might be considered a bug, since you are accessing the outer instance via a pointer contained in the inner instance. But I like the approach (even if it is a bug)! -Steve

Yes, you have similar bugs with delegates. This is a problem.
May 14 2012
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 5/14/2012 10:08 AM, Tove wrote:
 but c++ has the 'mutable' keyword as an easy escape route...

The existence of that capability means that 'const' in C++ cannot be meaningfully reasoned about.
May 14 2012
prev sibling parent Stewart Gordon <smjg_1998 yahoo.com> writes:
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
prev sibling next sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 14/05/2012 00:56, Mehrdad a écrit :
 On Sunday, 13 May 2012 at 22:51:05 UTC, Jonathan M Davis wrote:
 Anything that absolutely requires them will probably have to either
 have to break the type system or use _other_ functions with the same
 functionality but without those attributes. In some cases though,
 providing overloads which aren't const, pure, etc. should work though.
 If you want it to be otherwise, you're going to have to convince
 Walter, and I think that it's pretty clear that this is the way that
 it's going to have to be thanks to how const et al. work.

This is *exactly* the sort of problem I was referring to in my const(rant) thread, so to speak. These const-related issues make D, simply speaking, *HARD TO USE*. (Yes, that means even harder then C++ in some cases.) When people say it's painful to find workarounds to problems in D, I hope -- at the very least -- no one will be surprised as to why.

The only reason I'd see a toSting function as non pure or non const is memoize. It can be tackled with lib support in phobos. What are other uses cases ?
May 14 2012
next sibling parent =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <xtzgzorex gmail.com> writes:
On 14-05-2012 21:04, Mehrdad wrote:
 On Monday, 14 May 2012 at 18:52:06 UTC, deadalnix wrote:
 The only reason I'd see a toSting function as non pure or non const is
 memoize. It can be tackled with lib support in phobos.
 What are other uses cases ?

Not necessarily 'memoization' -- you could be instead querying another object for this information, which may not be const. (Say, a network connection.)

That's actually a very, very good point. -- - Alex
May 14 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 14/05/2012 21:04, Mehrdad a écrit :
 On Monday, 14 May 2012 at 18:52:06 UTC, deadalnix wrote:
 The only reason I'd see a toSting function as non pure or non const is
 memoize. It can be tackled with lib support in phobos.
 What are other uses cases ?

Not necessarily 'memoization' -- you could be instead querying another object for this information, which may not be const. (Say, a network connection.)

I'd argue that hiding such an operation in toString or similar stuff is what you want to avoid.
May 14 2012
parent deadalnix <deadalnix gmail.com> writes:
Le 14/05/2012 21:25, Mehrdad a écrit :
 On Monday, 14 May 2012 at 19:13:30 UTC, deadalnix wrote:
 Le 14/05/2012 21:04, Mehrdad a écrit :
 On Monday, 14 May 2012 at 18:52:06 UTC, deadalnix wrote:
 The only reason I'd see a toSting function as non pure or non const is
 memoize. It can be tackled with lib support in phobos.
 What are other uses cases ?

Not necessarily 'memoization' -- you could be instead querying another object for this information, which may not be const. (Say, a network connection.)

I'd argue that hiding such an operation in toString or similar stuff is what you want to avoid.

Uhm, I beg to differ... What, exactly, is wrong with asking a non-const object for your toString() information?

Nothing, expect it make it impossible to get that information on const/immutables objects. Additionnaly, this operation isn't supposed to modify the object in any meaningfull way, or perform complex operation like connecting a remote database.
May 14 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 14/05/2012 04:47, Jonathan M Davis a écrit :
 They can be in almost all cases. The problem is the folks who want to have
 caching and/or lazy initiailization in their classes/structs. You can't cache
 the result of any of those functions (toHash being the main target for it) if
 they're const and pure except in overloads which _aren't_ const, making those
 functions more expensive in comparison to what they could be if they weren't
 required to be const. And lazy initialization becomes almost impossible,
 because if the member variables needed in those functions haven't been
 initialized yet when they're called, then you _can't_ initialize them. If
 getting the value that it _would_ be without actually initializing the member
 variable works, then you can do that if it hasn't been initialized, but if you
 can't do that (e.g. the variable _must_ be set only once), then you're
 screwed. And regardless of whether you can make it work with const, it _will_
 be less efficient.

Lazy initialization is more a problem than a solution when it comes to multithreading. And I'm afraid it is the future. BTW, nothing prevent to define toString an non const and another as const. The const one cannot cache the result, but certainly can read it. And every method that is aware of the non const version will use it. This whole thing is overinflated. This isn't a problem. I'm pretty sure that most code that will broke was actually already a bad idea in the first place.
May 14 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 05/14/2012 09:18 PM, deadalnix wrote:
 Le 14/05/2012 04:47, Jonathan M Davis a écrit :
 They can be in almost all cases. The problem is the folks who want to
 have
 caching and/or lazy initiailization in their classes/structs. You
 can't cache
 the result of any of those functions (toHash being the main target for
 it) if
 they're const and pure except in overloads which _aren't_ const,
 making those
 functions more expensive in comparison to what they could be if they
 weren't
 required to be const. And lazy initialization becomes almost impossible,
 because if the member variables needed in those functions haven't been
 initialized yet when they're called, then you _can't_ initialize them. If
 getting the value that it _would_ be without actually initializing the
 member
 variable works, then you can do that if it hasn't been initialized,
 but if you
 can't do that (e.g. the variable _must_ be set only once), then you're
 screwed. And regardless of whether you can make it work with const, it
 _will_
 be less efficient.

Lazy initialization is more a problem than a solution when it comes to multithreading.

How to solve the problem of representation of infinite data structures otherwise?
 And I'm afraid it is the future.

Sharing mutable data frivolously is likely not the future. I guess that usually every thread will have its own cache.
 BTW, nothing prevent to define toString an non const and another as
 const. The const one cannot cache the result, but certainly can read it.
 And every method that is aware of the non const version will use it.

 This whole thing is overinflated. This isn't a problem. I'm pretty sure
 that most code that will broke was actually already a bad idea in the
 first place.

That is a rather optimistic assumption. And even if it is true for 'most' code, that is not enough imho.
May 14 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Sunday, 13 May 2012 at 22:51:05 UTC, Jonathan M Davis wrote:
 Anything that absolutely requires them will probably have to 
 either have to break the type system or use _other_ functions 
 with the same functionality but without those attributes. In 
 some cases though, providing overloads which aren't const, 
 pure, etc. should work though.
 If you want it to be otherwise, you're going to have to 
 convince Walter, and I think that it's pretty clear that this 
 is the way that it's going to have to be thanks to how const et 
 al. work.

This is *exactly* the sort of problem I was referring to in my const(rant) thread, so to speak. These const-related issues make D, simply speaking, *HARD TO USE*. (Yes, that means even harder then C++ in some cases.) When people say it's painful to find workarounds to problems in D, I hope -- at the very least -- no one will be surprised as to why.
May 13 2012
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Sunday, 13 May 2012 at 22:51:05 UTC, Jonathan M Davis wrote:
 Walter fully intends to require that opEquals, opCmp, toHash, 
 and toString all be const  safe pure nothrow - on both classes 
 and structs. And the reality of the matter is, if that 
 requirement _isn't_ there on classes, then the usage of any of 
 those functions in  safe, const, pure, or nothrow code is 
 seriously hampered - especially const and pure. This has been 
 discussed quite a bit before, and we're pretty much stuck 
 thanks to transitivity.

I've been wondering sometimes, if beta builds could be made (but not official release builds) that enforce the way it's suppose to be, and then we can try to compile against that version and see how much difference or how much breaks. The problem may not be quite as bad as we think it is. Honestly I don't see why oEquals, opCmp and toHash can't be const (as the data shouldn't change). toString and toHash may want to cache it's current result (perhaps?).
May 13 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 14 May 2012 at 18:52:06 UTC, deadalnix wrote:
 The only reason I'd see a toSting function as non pure or non 
 const is memoize. It can be tackled with lib support in phobos.
 What are other uses cases ?

Not necessarily 'memoization' -- you could be instead querying another object for this information, which may not be const. (Say, a network connection.)
May 14 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 14 May 2012 at 19:13:30 UTC, deadalnix wrote:
 Le 14/05/2012 21:04, Mehrdad a écrit :
 On Monday, 14 May 2012 at 18:52:06 UTC, deadalnix wrote:
 The only reason I'd see a toSting function as non pure or non 
 const is
 memoize. It can be tackled with lib support in phobos.
 What are other uses cases ?

Not necessarily 'memoization' -- you could be instead querying another object for this information, which may not be const. (Say, a network connection.)

I'd argue that hiding such an operation in toString or similar stuff is what you want to avoid.

Uhm, I beg to differ... What, exactly, is wrong with asking a non-const object for your toString() information?
May 14 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 14 May 2012 15:05:01 -0400, Alex R=C3=B8nne Petersen
<xtzgzorex gmail.com> wrote:

 On 14-05-2012 21:04, Mehrdad wrote:
 On Monday, 14 May 2012 at 18:52:06 UTC, deadalnix wrote:
 The only reason I'd see a toSting function as non pure or non const =



 memoize. It can be tackled with lib support in phobos.
 What are other uses cases ?

Not necessarily 'memoization' -- you could be instead querying another object for this information, which may not be const. (Say, a network connection.)

That's actually a very, very good point.

Yes, the use case for logical const is when an object *doesn't own* a reference. I proposed a logical const solution a long time ago that called it "nostate" instad of "mutable". But there is a really big problem with logical const that makes it not seem worth the trouble of having it -- the syntax. You would need a lo= t of keywords and/or features to correctly describe relationships between objects. I just don't think people would tolerate it. Hm... I just had a really good idea (I will divulge it in another post) = on how to implement logical const without altering the language and/or compiler. -Steve
May 14 2012
prev sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Monday, 14 May 2012 at 18:52:06 UTC, deadalnix wrote:
 The only reason I'd see a toSting function as non pure or non 
 const is memoize. It can be tackled with lib support in phobos.

 What are other uses cases ?

Did you miss my post? http://forum.dlang.org/post/azevkvzkhhfnpmtzgnvp forum.dlang.org
May 14 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Sunday, 13 May 2012 at 20:55:13 UTC, Jonathan M Davis wrote:
 toHash, opCmp, opEquals, and toString will _all_ be required to 
 be const  safe pure nothrow.

And what am I to do if my toString() method cannot be const?
May 13 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, May 13, 2012 at 12:42:32PM -0700, Walter Bright wrote:
[...]
 I'd like to see std.stream dumped. I don't see any reason for it to
 exist that std.stdio doesn't do (or should do).
 
 The only reason it's there at the moment is for backwards
 compatibility.

+1. I'd love to see std.io done in the near future. T -- Life is too short to run proprietary software. -- Bdale Garbee
May 13 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, May 14, 2012 02:11:21 Era Scarecrow wrote:
 On Sunday, 13 May 2012 at 22:51:05 UTC, Jonathan M Davis wrote:
 Walter fully intends to require that opEquals, opCmp, toHash,
 and toString all be const  safe pure nothrow - on both classes
 and structs. And the reality of the matter is, if that
 requirement _isn't_ there on classes, then the usage of any of
 those functions in  safe, const, pure, or nothrow code is
 seriously hampered - especially const and pure. This has been
 discussed quite a bit before, and we're pretty much stuck
 thanks to transitivity.

I've been wondering sometimes, if beta builds could be made (but not official release builds) that enforce the way it's suppose to be, and then we can try to compile against that version and see how much difference or how much breaks. The problem may not be quite as bad as we think it is.

It doesn't work right now due to issues with druntime and Phobos. For instance, format and to!string can't be pure yet due to lower level constructs that they use which aren't marked pure. Appender has similar problems. Without those, toString really can't be const. I think that there are some issues with toHash as well due to the state of the AA implementation in druntime. Some of those issues are being addressed, but there's still a ways to go. But in a way, it doesn't matter how much making those 4 functions const safe pure nothrow will break, because we _have_ to do it. So, what will break, will break. When they're made const safe pure nothrow on Object, it'll _immediately_ break code, and there's _no_ way around it. So, at this point, I'd say that it's really a matter of figuring out exactly what needs to be fixed in druntime and Phobos to make it possible, and once that's been fixed, we immediately change Object and and require that those 4 functions have those 4 attributes. Then everyone can take care of it in their code at once and we won't have to worry about it anymore. Lacking a means of phasing it in (like we're doing with other stuff like -property), I don't see any other way of doing it.
 Honestly I don't see why oEquals,
 opCmp and toHash can't be const (as the data shouldn't change).
 toString and toHash may want to cache it's current result
 (perhaps?).

They can be in almost all cases. The problem is the folks who want to have caching and/or lazy initiailization in their classes/structs. You can't cache the result of any of those functions (toHash being the main target for it) if they're const and pure except in overloads which _aren't_ const, making those functions more expensive in comparison to what they could be if they weren't required to be const. And lazy initialization becomes almost impossible, because if the member variables needed in those functions haven't been initialized yet when they're called, then you _can't_ initialize them. If getting the value that it _would_ be without actually initializing the member variable works, then you can do that if it hasn't been initialized, but if you can't do that (e.g. the variable _must_ be set only once), then you're screwed. And regardless of whether you can make it work with const, it _will_ be less efficient. So, the folks who really like to use lazy initialization and caching are _not_ going to be happy about this. They've complained every time that it's been discussed. But that's just one of the costs of const in D. Whether its pros outweigh that cost is a matter of opinion. - Jonathan M Davis
May 13 2012
prev sibling next sibling parent reply "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 14 May 2012 at 02:48:20 UTC, Jonathan M Davis wrote:
 But that's just one of the costs of const in D.

The problem is that it's unavoidable. i.e. you can't say "don't mark it as const if it isn't const", because, practically speaking, it's being forced onto the programmers by the language.
May 13 2012
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 5/13/2012 8:19 PM, Jonathan M Davis wrote:
 This is in definite contrast to C++ where you
 _can_ avoid const completely if you want to.

That's because C++ const is just documentation for the programmer, and offers no reliable enforcement.
May 13 2012
prev sibling parent reply =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <xtzgzorex gmail.com> writes:
On 14-05-2012 05:19, Jonathan M Davis wrote:
 On Monday, May 14, 2012 05:09:28 Era Scarecrow wrote:
 On Monday, 14 May 2012 at 02:57:57 UTC, Mehrdad wrote:
 On Monday, 14 May 2012 at 02:48:20 UTC, Jonathan M Davis wrote:
 But that's just one of the costs of const in D.

The problem is that it's unavoidable. I.e. you can't say "don't Mark it as const if it isn't const", because, practically speaking, it's being forced onto the programmers by the language.

With const and immutable being transitive, there are cases when there's things you want to do with an object after you've made it and it's in a const structure. I know I've made a struct that was intended to hold immutable data, however it refused to allow methods to until const was appended to the signatures of the functions. At first it was annoying; But when I see bigger picture of why it makes much more sense. Nothing we make 'has' to have const on it but inevitably they may be used in a read-only way. I'm trying to think of it as my structs and classes being as const-friendly as possible in those cases.

In general, you _can_ avoid const completely if you want to. However, with opEquals, opCmp, toHash, and toString being const safe pure nothrow, you _can't_ avoid it completely - at least not entirely cleanly.

Don't forget contracts. The this reference is now const inside those too. This can get painful sometimes when you really don't want the this reference to be const inside your invariants. With 2.058, I had to insert a lot of cast()s to throw const away inside those. So... effectively, it's pretty hard to avoid const unless you don't use contracts at all.
 You could have non-const overloads which did what you wanted and make the
 normal const ones assert(0) if they're called. But you're basically forced to
 work around const in this case. This is in definite contrast to C++ where you
 _can_ avoid const completely if you want to.

 But with the way const in D works, I suspect that the folks who are looking
 for absolutely every CPU cycle and want caching and lazy-loading in their
 types are going to avoid const as completely as possible and figure out how to
 work around it for those 4 functions, whereas in C++, they'd probably use
 const liberally and use mutable where necessary.

 - Jonathan M Davis

It's kinda funny that something which on dlang.org's FAQ is described as a compiler optimization hint (http://dlang.org/const-faq.html#const) isn't useful at all for this purpose... -- - Alex
May 13 2012
next sibling parent reply =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <xtzgzorex gmail.com> writes:
On 14-05-2012 06:18, Jonathan M Davis wrote:
 On Monday, May 14, 2012 05:47:54 Alex Rønne Petersen wrote:
 It's kinda funny that something which on dlang.org's FAQ is described as
 a compiler optimization hint (http://dlang.org/const-faq.html#const)
 isn't useful at all for this purpose...

Oh, it's definitely useful for optimizations. It just doesn't work with a couple of idioms that some programmers like to use in order to optimize their code. - Jonathan M Davis

I have yet to see any compiler make sensible use of the information provided by both C++'s const and D's const. const in particular is completely useless to an optimizer because it does not give it any information that it can use for anything. The kind of information that an optimization pass, in general, wants to see is whether something is guaranteed to *never* change. const does not provide this information. const simply guarantees that the code working on the const data cannot alter it (but at the same time allows *other* code to alter it), which, as said, is useless to the optimizer. immutable is a different story. immutable actually opens the door to many optimization opportunities exactly because the optimizer knows that the data will not be altered, ever. This allows it to (almost) arbitrarily reorder code, fold many computations at compile time, do conditional constant propagation, dead code elimination, ... This seems reasonable. But now consider that the majority of functions *are written for const, not immutable*. Thereby, you're throwing away the immutable guarantee, which is what the *compiler* (not the *programmer*) cares about. immutable is an excellent idea in theory, but in practice, it doesn't help the compiler because you'd have to either a) templatize all functions operating on const/immutable data so the compiler can retain the immutable guarantee when the input is such, or b) explicitly duplicate code for the const and the immutable case. Both approaches clearly suck. Templates don't play nice with polymorphism, and code duplication is...well...duplication. So, most of druntime and phobos is written for const because const is the bridge between the mutable and immutable world, and writing code against that rather than explicitly against mutable/immutable data is just simpler. But this completely ruins any opportunity the compiler has to optimize! (An interesting fact is that even the compiler engineers working on compilers for strictly pure functional languages have yet to take full advantage of the potential that a pure, immutable world offers. If *they* haven't done it yet, I don't think we're going to do it for a long time to come.) Now, you might argue that the compiler could simply say "okay, this data is const, which means it cannot be changed in this particular piece of code and thus nowhere else, since it is not explicitly shared, and therefore not touched by any other threads". This would be great if shared wasn't a complete design fallacy. Unfortunately, in most real world code, shared just doesn't cut it, and data is often shared between threads without using the shared qualifier (__gshared is one example). shared is another can of worms entirely. I can list a few initial reasons why it's unrealistic and impractical: 1) It is extremely x86-biased; implementing it on other architectures is going to be...interesting (read: on many architectures, impossible at ISA level). 2) There is no bridge between shared and unshared like there is for mutable and immutable. This means that all code operating on shared data has to be templatized (no, casts will not suffice; the compiler can't insert memory barriers then) or code has to be explicitly duplicated for the shared and unshared case. Funnily, the exact same issue mentioned above for const and immutable! 3) It only provides documentation value. The low-level atomicity that it is supposed to provide (but doesn't yet...) is of extremely questionable value. In my experience, I never actually access shared data from multiple threads simultaneously, but rather, transfer the data from one thread to another and use it exclusively in the other thread (i.e. handing over the ownership). In such scenarios, shared just adds overhead (memory barriers are Bad (TM) for performance). /rant -- - Alex
May 13 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 5/13/2012 10:34 PM, Alex Rønne Petersen wrote:
 I have yet to see any compiler make sensible use of the information provided by
 both C++'s const and D's const.

D's const is part of purity, which is optimizable.
 const in particular is completely useless to an optimizer because it does not
 give it any information that it can use for anything. The kind of information
 that an optimization pass, in general, wants to see is whether something is
 guaranteed to *never* change. const does not provide this information. const
 simply guarantees that the code working on the const data cannot alter it (but
 at the same time allows *other* code to alter it), which, as said, is useless
to
 the optimizer.

 immutable is a different story. immutable actually opens the door to many
 optimization opportunities exactly because the optimizer knows that the data
 will not be altered, ever. This allows it to (almost) arbitrarily reorder code,
 fold many computations at compile time, do conditional constant propagation,
 dead code elimination, ...

You cannot have immutable without also having const. Or, at least, it would be impractical.
 This seems reasonable. But now consider that the majority of functions *are
 written for const, not immutable*. Thereby, you're throwing away the immutable
 guarantee, which is what the *compiler* (not the *programmer*) cares about.
 immutable is an excellent idea in theory, but in practice, it doesn't help the
 compiler because you'd have to either

 a) templatize all functions operating on const/immutable data so the compiler
 can retain the immutable guarantee when the input is such, or
 b) explicitly duplicate code for the const and the immutable case.

strings are immutable, not just const. It's been very successful.
 Both approaches clearly suck. Templates don't play nice with polymorphism, and
 code duplication is...well...duplication. So, most of druntime and phobos is
 written for const because const is the bridge between the mutable and immutable
 world, and writing code against that rather than explicitly against
 mutable/immutable data is just simpler. But this completely ruins any
 opportunity the compiler has to optimize!

That isn't true when it comes to purity.
 (An interesting fact is that even the compiler engineers working on compilers
 for strictly pure functional languages have yet to take full advantage of the
 potential that a pure, immutable world offers. If *they* haven't done it yet, I
 don't think we're going to do it for a long time to come.)

It isn't just what the compiler can do, purity and immutability offer a means to prove things about code.
 Now, you might argue that the compiler could simply say "okay, this data is
 const, which means it cannot be changed in this particular piece of code and
 thus nowhere else, since it is not explicitly shared, and therefore not touched
 by any other threads". This would be great if shared wasn't a complete design
 fallacy. Unfortunately, in most real world code, shared just doesn't cut it,
and
 data is often shared between threads without using the shared qualifier
 (__gshared is one example).

Yes, if you're thinking like a C programmer!
 shared is another can of worms entirely. I can list a few initial reasons why
 it's unrealistic and impractical:

 1) It is extremely x86-biased; implementing it on other architectures is going
 to be...interesting (read: on many architectures, impossible at ISA level).

I don't see why.
 2) There is no bridge between shared and unshared like there is for mutable and
 immutable. This means that all code operating on shared data has to be
 templatized (no, casts will not suffice; the compiler can't insert memory
 barriers then) or code has to be explicitly duplicated for the shared and
 unshared case. Funnily, the exact same issue mentioned above for const and
 immutable!

Frankly, you're doing it wrong if you're doing more than trivial things with shared types. Running an algorithm on a shared type is just a bad idea.
 3) It only provides documentation value. The low-level atomicity that it is
 supposed to provide (but doesn't yet...) is of extremely questionable value. In
 my experience, I never actually access shared data from multiple threads
 simultaneously, but rather, transfer the data from one thread to another and
use
 it exclusively in the other thread (i.e. handing over the ownership). In such
 scenarios, shared just adds overhead (memory barriers are Bad (TM) for
 performance).

Transferring data between threads should be done either using value types, which are copied, or references which are typed as shared only transitorially.
May 13 2012
parent =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <xtzgzorex gmail.com> writes:
On 14-05-2012 08:37, Walter Bright wrote:
 On 5/13/2012 10:34 PM, Alex Rønne Petersen wrote:
 I have yet to see any compiler make sensible use of the information
 provided by
 both C++'s const and D's const.

D's const is part of purity, which is optimizable.

A function can still be weakly pure and operating on const arguments, meaning you *still* have no opportunity for optimization. Strongly pure functions are easily optimizable - because they operate on immutable data (or, at least, data with shared indirection).
 const in particular is completely useless to an optimizer because it
 does not
 give it any information that it can use for anything. The kind of
 information
 that an optimization pass, in general, wants to see is whether
 something is
 guaranteed to *never* change. const does not provide this information.
 const
 simply guarantees that the code working on the const data cannot alter
 it (but
 at the same time allows *other* code to alter it), which, as said, is
 useless to
 the optimizer.

 immutable is a different story. immutable actually opens the door to many
 optimization opportunities exactly because the optimizer knows that
 the data
 will not be altered, ever. This allows it to (almost) arbitrarily
 reorder code,
 fold many computations at compile time, do conditional constant
 propagation,
 dead code elimination, ...

You cannot have immutable without also having const. Or, at least, it would be impractical.

I agree entirely. I'm just saying that the way it is in the language right now doesn't make it easy for the compiler to optimize for immutable at all, due to how programmers tend to program against it.
 This seems reasonable. But now consider that the majority of functions
 *are
 written for const, not immutable*. Thereby, you're throwing away the
 immutable
 guarantee, which is what the *compiler* (not the *programmer*) cares
 about.
 immutable is an excellent idea in theory, but in practice, it doesn't
 help the
 compiler because you'd have to either

 a) templatize all functions operating on const/immutable data so the
 compiler
 can retain the immutable guarantee when the input is such, or
 b) explicitly duplicate code for the const and the immutable case.

strings are immutable, not just const. It's been very successful.

And yet, the majority of functions operate on const(char)[], not immutable(char)[], thereby removing the guarantee that string was supposed to give about immutability.
 Both approaches clearly suck. Templates don't play nice with
 polymorphism, and
 code duplication is...well...duplication. So, most of druntime and
 phobos is
 written for const because const is the bridge between the mutable and
 immutable
 world, and writing code against that rather than explicitly against
 mutable/immutable data is just simpler. But this completely ruins any
 opportunity the compiler has to optimize!

That isn't true when it comes to purity.

I don't follow. Can you elaborate?
 (An interesting fact is that even the compiler engineers working on
 compilers
 for strictly pure functional languages have yet to take full advantage
 of the
 potential that a pure, immutable world offers. If *they* haven't done
 it yet, I
 don't think we're going to do it for a long time to come.)

It isn't just what the compiler can do, purity and immutability offer a means to prove things about code.

Absolutely, and I think that has significant value. Keep in mind that I am only contesting the usefulness of const in terms of optimizations in a normal compiler.
 Now, you might argue that the compiler could simply say "okay, this
 data is
 const, which means it cannot be changed in this particular piece of
 code and
 thus nowhere else, since it is not explicitly shared, and therefore
 not touched
 by any other threads". This would be great if shared wasn't a complete
 design
 fallacy. Unfortunately, in most real world code, shared just doesn't
 cut it, and
 data is often shared between threads without using the shared qualifier
 (__gshared is one example).

Yes, if you're thinking like a C programmer!

Or if you're doing low-level thread programming (in my case, for a virtual machine).
 shared is another can of worms entirely. I can list a few initial
 reasons why
 it's unrealistic and impractical:

 1) It is extremely x86-biased; implementing it on other architectures
 is going
 to be...interesting (read: on many architectures, impossible at ISA
 level).

I don't see why.

Some architectures with weak memory models just plain don't have fence instructions.
 2) There is no bridge between shared and unshared like there is for
 mutable and
 immutable. This means that all code operating on shared data has to be
 templatized (no, casts will not suffice; the compiler can't insert memory
 barriers then) or code has to be explicitly duplicated for the shared and
 unshared case. Funnily, the exact same issue mentioned above for const
 and
 immutable!

Frankly, you're doing it wrong if you're doing more than trivial things with shared types. Running an algorithm on a shared type is just a bad idea.

So you're saying that casting shared away when dealing with message-passing is the right thing to do? (Using immutable is not always the answer...)
 3) It only provides documentation value. The low-level atomicity that
 it is
 supposed to provide (but doesn't yet...) is of extremely questionable
 value. In
 my experience, I never actually access shared data from multiple threads
 simultaneously, but rather, transfer the data from one thread to
 another and use
 it exclusively in the other thread (i.e. handing over the ownership).
 In such
 scenarios, shared just adds overhead (memory barriers are Bad (TM) for
 performance).

Transferring data between threads should be done either using value types, which are copied, or references which are typed as shared only transitorially.

But with references come the issue that they are practically unusable with shared, forcing one to cast it away. This should be a clear sign that the feature is incomplete. -- - Alex
May 14 2012
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 5/13/2012 11:39 PM, Era Scarecrow wrote:
 I'm still trying to find a good OO book that will teach me how to think and
 build with OOP properly. I'm probably only half as effective as I could be. If
 you have any good book recommendations I'll try and get ahold of them.

The classic is Object-Oriented Programming by Bertrand Meyer.
May 13 2012
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 14 May 2012 at 02:57:57 UTC, Mehrdad wrote:
 On Monday, 14 May 2012 at 02:48:20 UTC, Jonathan M Davis wrote:
 But that's just one of the costs of const in D.

The problem is that it's unavoidable. I.e. you can't say "don't Mark it as const if it isn't const", because, practically speaking, it's being forced onto the programmers by the language.

With const and immutable being transitive, there are cases when there's things you want to do with an object after you've made it and it's in a const structure. I know I've made a struct that was intended to hold immutable data, however it refused to allow methods to until const was appended to the signatures of the functions. At first it was annoying; But when I see bigger picture of why it makes much more sense. Nothing we make 'has' to have const on it but inevitably they may be used in a read-only way. I'm trying to think of it as my structs and classes being as const-friendly as possible in those cases.
May 13 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, May 14, 2012 05:09:28 Era Scarecrow wrote:
 On Monday, 14 May 2012 at 02:57:57 UTC, Mehrdad wrote:
 On Monday, 14 May 2012 at 02:48:20 UTC, Jonathan M Davis wrote:
 But that's just one of the costs of const in D.

The problem is that it's unavoidable. I.e. you can't say "don't Mark it as const if it isn't const", because, practically speaking, it's being forced onto the programmers by the language.

With const and immutable being transitive, there are cases when there's things you want to do with an object after you've made it and it's in a const structure. I know I've made a struct that was intended to hold immutable data, however it refused to allow methods to until const was appended to the signatures of the functions. At first it was annoying; But when I see bigger picture of why it makes much more sense. Nothing we make 'has' to have const on it but inevitably they may be used in a read-only way. I'm trying to think of it as my structs and classes being as const-friendly as possible in those cases.

In general, you _can_ avoid const completely if you want to. However, with opEquals, opCmp, toHash, and toString being const safe pure nothrow, you _can't_ avoid it completely - at least not entirely cleanly. You could have non-const overloads which did what you wanted and make the normal const ones assert(0) if they're called. But you're basically forced to work around const in this case. This is in definite contrast to C++ where you _can_ avoid const completely if you want to. But with the way const in D works, I suspect that the folks who are looking for absolutely every CPU cycle and want caching and lazy-loading in their types are going to avoid const as completely as possible and figure out how to work around it for those 4 functions, whereas in C++, they'd probably use const liberally and use mutable where necessary. - Jonathan M Davis
May 13 2012
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 14 May 2012 at 03:19:57 UTC, Jonathan M Davis wrote:
 But with the way const in D works, I suspect that the folks who 
 are looking for absolutely every CPU cycle and want caching and 
 lazy-loading in their types are going to avoid const as 
 completely as possible and figure out how to work around it for 
 those 4 functions, whereas in C++, they'd probably use const 
 liberally and use mutable where necessary.>

Seems like perhaps the opposite approach (limited of course) could be taken. Assume a 'mutable' keyword was used, where in the case an object/struct was made const, it would allow you to break the const system only for that variable. Could work, assuming it's not accessing ROM or something. struct A { string st; mutable uint hash; //always mutable uint toHash() { if (!hash) hash = st.toHash(); return hash; } } unittest{ A a = A("TEST"); immutable A b = A("TEST"); assert(a.toHash() == b.toHash()); } But that seems like a badly done workaround, and if you really wanted the hash cached, you would have it calculated during construction before it was returned. Something like that is likely more trouble than it's worth, plus it would easily be abused.
May 13 2012
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Monday, 14 May 2012 at 02:57:57 UTC, Mehrdad wrote:
 The problem is that it's unavoidable.

 i.e. you can't say "don't mark it as const if it isn't const", 
 because, practically speaking, it's being forced onto the 
 programmers by the language.

You're really against const in this language, huh? Let me ask you something: Could you try to name 2 or 3 good things about const/immutable without looking it up? If not, you've really not given enough effort to learning about the benefits of it.
May 13 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, May 14, 2012 05:47:54 Alex R=C3=B8nne Petersen wrote:
 It's kinda funny that something which on dlang.org's FAQ is described=

 a compiler optimization hint (http://dlang.org/const-faq.html#const)
 isn't useful at all for this purpose...

Oh, it's definitely useful for optimizations. It just doesn't work with= a=20 couple of idioms that some programmers like to use in order to optimize= their=20 code. - Jonathan M Davis
May 13 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, May 13, 2012 20:40:51 Walter Bright wrote:
 On 5/13/2012 8:19 PM, Jonathan M Davis wrote:
 This is in definite contrast to C++ where you
 _can_ avoid const completely if you want to.

That's because C++ const is just documentation for the programmer, and offers no reliable enforcement.

May 13 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, May 13, 2012 20:40:51 Walter Bright wrote:
 On 5/13/2012 8:19 PM, Jonathan M Davis wrote:
 This is in definite contrast to C++ where you
 _can_ avoid const completely if you want to.

That's because C++ const is just documentation for the programmer, and offers no reliable enforcement.

True, but the point is that the pros and cons of D's const and C++'s const are different, and wihle D's const is more powerful, that power does come with a cost, and it's difficult to avoid D's const completely, whereas C++'s const is quite easy to avoid. - Jonathan M Davis
May 13 2012
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 14 May 2012 at 04:18:32 UTC, Jonathan M Davis wrote:
 Oh, it's definitely useful for optimizations. It just doesn't 
 work with a couple of idioms that some programmers like to use 
 in order to optimize their code.

Perhaps bad practices and styles hard-coded in their psyche due to limitations and issues with the other language? If there's something that gets in the way with what's made sense to you; You'll either try to figure it out (manual and forums) or backtrack to what you do understand and perhaps get even more stuck than before. I was in the military for a time; In Basic Training they told us 'forget everything (you think) you know about firing your rifle'. They taught you from scratch how they wanted you to do it. In programming there's no real 'from scratch' after you're used to it a certain set of ways, and relearning something that already makes sense to you makes you unconsciously skip things you otherwise wouldn't.
May 13 2012
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, May 14, 2012 at 06:55:02AM +0200, Era Scarecrow wrote:
[...]
  I was in the military for a time; In Basic Training they told us
 'forget everything (you think) you know about firing your rifle'.
 They taught you from scratch how they wanted you to do it. In
 programming there's no real 'from scratch' after you're used to it a
 certain set of ways, and relearning something that already makes
 sense to you makes you unconsciously skip things you otherwise
 wouldn't.

Part of the pitfall of D being part of the C-derived language family is that people (mainly the C++/Java/C# crowd) unconsciously carry over their assumptions of how things work from the other languages, and they try to do things the same way in D and keep running into barriers, because D just isn't designed to work that way. That's part of the reason I didn't really get comfortable with D programming until I bought TDPL -- I needed to learn D "from scratch", as it were, to think about it from a fresh perspective instead of bringing along my years of C/C++ baggage. For that, looking at a bunch of online reference docs didn't help: you're just learning the "vocabulary", as it were, and not really "thinking in the language". As any foreign language learner knows, you will never speak the language well if you just keep translating from your native language; you have to learn to "think in that language". I needed to read through TDPL like a newbie in order to learn to write D the way it's supposed to be written. Once I started doing that, many things began to make a lot more sense. T -- Try to keep an open mind, but not so open your brain falls out. -- theboz
May 13 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 14 May 2012 at 03:19:57 UTC, Jonathan M Davis wrote:
 I suspect that the folks who are looking for absolutely every 
 CPU cycle and want caching and lazy-loading in their types

It's not a CPU cycle issue. Caching/lazy-loading can make the difference between a window that freezes, and a window that doesn't.
May 13 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 14 May 2012 at 04:10:30 UTC, Chris Cain wrote:
 Let me ask you something: Could you try to name 2 or 3 good 
 things about const/immutable without looking it up? If not, 
 you've really not given enough effort to learning about the 
 benefits of it.

(1) Compiler helps you write correct multithreaded code (2) You help compiler perform optimizations based on contracts (3) I don't think there exists a #3 that's very different from #1 and #2 I'm not saying const is bad (at least, that's not what I'm saying *here*). What I'm saying is that it's being forced upon the user, and it's becoming unavoidable. Which means people who don't like it won't use D. Kind of like the GC.
May 13 2012
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 14 May 2012 at 05:31:01 UTC, H. S. Teoh wrote:> That's 
part of the reason I didn't really get comfortable with D 
programming until I bought TDPL -- I needed to learn D "from 
scratch", as it were, to think about it from a fresh perspective 
instead of bringing along my years of C/C++ baggage. For that, 
looking at a bunch of online reference docs didn't help: you're 
just learning the "vocabulary", as it were, and not really 
"thinking in the language". As any foreign language learner 
knows, you will never speak the language well if you just keep 
translating from your native language; you have to learn to 
"think in that language". I needed to read through TDPL like a 
newbie in order to learn to write D the way it's supposed to be 
written.
 Once I started doing that, many things began to make a lot more 
 sense.

Exactly! If you don't have a good concise enough overview or explanation of the language and features you end up with guesswork from all over the place. Thankfully my only OO experience beyond D2 has been Java, and that only helped me understand polymorphism and interfaces, giving me what I needed without cluttering me with the necessaries of the language. I am perhaps as 'unlearned' as I can be, aside from doing some ASM/C work (and going away from pointer and -> manipulation is nice :) ) I'm still trying to find a good OO book that will teach me how to think and build with OOP properly. I'm probably only half as effective as I could be. If you have any good book recommendations I'll try and get ahold of them.
May 13 2012
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
On Monday, 14 May 2012 at 06:27:15 UTC, Walter Bright wrote:
 On 5/13/2012 11:09 PM, Mehrdad wrote:
 (1) Compiler helps you write correct multithreaded code
 (2) You help compiler perform optimizations based on contracts
 (3) I don't think there exists a #3 that's very different from 
 #1 and #2

#3 Improves self-documentation of code - it's more understandable and less susceptible to breakage during maintenance. #4 Improves encapsulation #5 Makes function purity possible

And all this together culminates into a much greater ability to _reason about code_. Suppose I have code like this: immutable item = ...; auto res = aPureFunct(item); // tons of code auto calc = someVal * aPureFunct(item); Barring the fact that the compiler will probably optimize this out for you in D... If you were writing C++ code like this, you couldn't tell _almost anything_ about this code based on that fragment I gave you. The mere fact that I can tell you _something_ is shocking. I can figure out properties and reason about code, even in small chunks like this. I know that I can replace "aPureFunct(item)" with res and it'll be equivalent. I'd say that this is my favorite reason (but non-obvious unless you actually code using const/immutable): #6 Reduced cognitive load I posted this elsewhere, but it also ties multiple things together (including the GC): How about if you're trying to figure out how often a substring of length 10 is repeated in some large string (maybe 4 million characters long?). In D: uint[string] countmap; foreach(i; 0..str.length-10) ++countmap[str[i..i+10]]; And you're done. Because your string is immutable, D will do the correct thing and use pointer+length to the original huge string and won't waste time copying things that are guaranteed not to change into its hash table. Thanks to the GC, you don't have to worry about watching your pointers to make sure they get deleted whenever it's appropriate (when you're done, you can just clear out countmap and the GC will collect the string if it's not used by anyone else...). Presumably the associative array implementation could take advantage of both the immutability of the string and the purity of toHash to cache the result of hashing (although, I'm not certain it does). Now imagine the code you'd have to write in C++ or many other languages to do this _right_. Not just fast, but _also_ correct. The fact of the matter is that, considering all of D's features, the whole is greater than the sum of its parts. Can you find something wrong with each part? Of course. But the fact of the matter is that each of them support each other in such a way that they synergistically improve the language ... if you take any one of them away, you would be left with everything else being less useful than before. This is why you really ought to spend some time with const and immutable (and, yeah, they're mostly inseparable) and really get to know what it enables you to do. Tons of features and capabilities depend on it, so it's really not something that "should be gotten rid of to make the language more marketable". I'm not saying I wouldn't use D if we lost const/immutable, but it would certainly sour my experience with it.
May 14 2012
prev sibling next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
On Monday, 14 May 2012 at 06:27:15 UTC, Walter Bright wrote:
 #3 Improves self-documentation of code - it's more 
 understandable and less susceptible to breakage during 
 maintenance.

#3 is also valid for C++, so I wasn't exactly considering that.
 #4 Improves encapsulation

o.O How does it improve encapsulation?
 #5 Makes function purity possible

Ah yea, this one I forgot.
May 14 2012
prev sibling next sibling parent "John" <nospam unavailable.com> writes:
On Monday, 14 May 2012 at 06:04:33 UTC, Mehrdad wrote:
 On Monday, 14 May 2012 at 03:19:57 UTC, Jonathan M Davis wrote:
 I suspect that the folks who are looking for absolutely every 
 CPU cycle and want caching and lazy-loading in their types

It's not a CPU cycle issue. Caching/lazy-loading can make the difference between a window that freezes, and a window that doesn't.

You can still do this pretty easily. You just have to cast away const. You can even make it reusable. mixin template TrustedToString() { const safe pure nothrow string toString() { return unsafeToString(); } final const trusted pure nothrow string unsafeToString() { auto nonConstThis = cast(Unqual!(typeof(this)))this; return nonConstThis.trustedToString(); } } class Foo { public: const safe pure nothrow string toString() { return "Foo"; } } class Bar : Foo { public: mixin TrustedToString; final pure trusted nothrow string trustedToString() { if (cachedString.length == 0) { cachedString = "Bar"; } return cachedString; } string cachedString; } unittest { auto f = new Foo(); assert(f.toString() == "Foo"); auto b = new Bar(); assert(b.toString() == "Bar"); }
May 14 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, May 14, 2012 15:27:41 John wrote:
 On Monday, 14 May 2012 at 06:04:33 UTC, Mehrdad wrote:
 On Monday, 14 May 2012 at 03:19:57 UTC, Jonathan M Davis wrote:
 I suspect that the folks who are looking for absolutely every
 CPU cycle and want caching and lazy-loading in their types

It's not a CPU cycle issue. Caching/lazy-loading can make the difference between a window that freezes, and a window that doesn't.

You can still do this pretty easily. You just have to cast away const.

But that breaks the type system. http://stackoverflow.com/questions/4219600/logical-const-in-d Yes, you _can_ do it, but it's not actually guaranteed to work (casting away const and mutating a variable is _undefined_), and if you do it with an object which is actually immutable, then things could go _very_ wrong (e.g. segfault). - Jonathan M Davis
May 14 2012
prev sibling next sibling parent "Tove" <tove fransson.se> writes:
On Monday, 14 May 2012 at 16:53:24 UTC, Timon Gehr wrote:
 On 05/14/2012 06:10 AM, Chris Cain wrote:
 On Monday, 14 May 2012 at 02:57:57 UTC, Mehrdad wrote:
 The problem is that it's unavoidable.

 i.e. you can't say "don't mark it as const if it isn't const",
 because, practically speaking, it's being forced onto the 
 programmers
 by the language.

You're really against const in this language, huh?

I guess this is not the most important point. He has been trying to use const like in OO-ish C++. This just does not work, because D const is detrimental to OO principles when used that way. The proposal is about _enforcing_ C++-like usage of const.

but c++ has the 'mutable' keyword as an easy escape route... which saved me a bunch of times... guess one can emulate it with a library-solution using nested classes? But... what about structs? 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; }
May 14 2012
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 14 May 2012 13:08:06 -0400, Tove <tove fransson.se> wrote:

 On Monday, 14 May 2012 at 16:53:24 UTC, Timon Gehr wrote:
 On 05/14/2012 06:10 AM, Chris Cain wrote:
 On Monday, 14 May 2012 at 02:57:57 UTC, Mehrdad wrote:
 The problem is that it's unavoidable.

 i.e. you can't say "don't mark it as const if it isn't const",
 because, practically speaking, it's being forced onto the programmers
 by the language.

You're really against const in this language, huh?

I guess this is not the most important point. He has been trying to use const like in OO-ish C++. This just does not work, because D const is detrimental to OO principles when used that way. The proposal is about _enforcing_ C++-like usage of const.

but c++ has the 'mutable' keyword as an easy escape route... which saved me a bunch of times... guess one can emulate it with a library-solution using nested classes? But... what about structs? 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; }

I have never seen this suggested before. I would guess that it might be considered a bug, since you are accessing the outer instance via a pointer contained in the inner instance. But I like the approach (even if it is a bug)! -Steve
May 14 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, May 14, 2012 21:18:42 deadalnix wrote:
 This whole thing is overinflated. This isn't a problem. I'm pretty sure
 that most code that will broke was actually already a bad idea in the
 first place.

I'm fine with it, but there are people who complain quite bitterly about it (generally game programmers, I think - people with pretty insane restrictions on their programs due to very strict performance requirements). - Jonathan M Davis
May 14 2012
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 14 May 2012 at 19:24:15 UTC, Steven Schveighoffer 
wrote:
 I have never seen this suggested before.  I would guess that it 
 might be considered a bug, since you are accessing the outer 
 instance via a pointer contained in the inner instance.

 But I like the approach (even if it is a bug)!

I was thinking something along these lines last night, although I couldn't put it quite together, it did end up using alias this. I forget but someone commented on possibly using a flag that would let you write to a variable for the first time, and break if you tried to read from it. A thought that went through my head would indeed need a little thought, but I couldn't see an easy way to do it outside of a new keyword. What if .init (or null) was your flag effectively? Let's assume allowinit (or something better) to be the keyword. Then... struct S { allowinit uint hash; uint toHash() const { if (hash == uint.init) { //read always allowed safely within struct hash = 1; } return hash; } void throwtest() { hash = 2; //might not throw } } func(){ const S s, z; // writeln(s.hash); //throw! //throwtest(); //would not throw here writeln(z.toHash()); //fine writeln(z.toHash()); //yep still fine writeln(z.hash); //allowed? technically yes... throwtest(); //throw! } Course these types may cause more problems if outside code had to do extra checks, so making allowinit forced to be private would remove that issue leaving it only to within the struct, making it good for caching results as you could only set it once. But that only works with const data, immutable being locked in memory it wouldn't do the job. So the only way to get around that is to allow a constructor that sets it during construction. //as above struct S { this(uint h) { hash = h; } } func(){ immutable S i; //compile error! Immutable cannot have allowinit with default constructor! immutable S i2 = S(10); //fine immutable S i2 = S(uint.init); //error! allowinit cannot be assigned to .init value for immutable! }
May 14 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Walter Bright:

 Being able to reason about code is *intensely* important. All 
 those decorations - const, pure, nothrow, out - serve to help 
 that out.

Bertrand Meyer (Eiffel author) is now working a lot on this, as you see from many of the last posts on his blog (http://bertrandmeyer.com/ ). The "Modern Eiffel" language is being designed right to allow more reasoning about code. This seems a small trend in new languages, but I can't predict how much important it will become in ten years. Bye, bearophile
May 15 2012
prev sibling next sibling parent reply Chad J <chadjoan __spam.is.bad__gmail.com> writes:
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
next sibling parent reply travert phare.normalesup.org (Christophe) writes:
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
parent reply Chad J <chadjoan __spam.is.bad__gmail.com> writes:
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
parent reply travert phare.normalesup.org (Christophe) writes:
Chad J , dans le message (digitalmars.D:167472), a écrit :
 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.

I was a bit fast on my post, but it cannot work: Something has to be modified both normally by a non-const function to invalidate the cache, and exceptionally by the const function to set the cache. Two remarks about people trying to solve the const problem: - People trying to modify a const value by using holes like delegates and inner classes might as well cast away const. - People trying to associate mutable members to a const object might as well ask for a mutable keyword to have members that stays mutable even if the object is const (and remove power to class with mutable members, such as the possibility to be immutable, have strongly pure methods, etc.). It will have the same effect as other proposed work arround, but at least it is easy to understand how it works and won't cripple the langage with too many awkward rules. After all, D is a mutli-paradigm programming langage. It should not force programmers to use hard constness. Better use as clean as possible mutable data than complicated workarrounds.
May 16 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 05/16/2012 10:17 AM, Christophe wrote:
 [...]  mutable keyword to have members that stays mutable even
 if the object is const (and remove power to class with  mutable members,
 such as the possibility to be immutable, have strongly pure methods,
 etc.). It will have the same effect as other proposed work arround, but
 at least it is easy to understand how it works and won't cripple the
 langage with too many awkward rules.

I think this is a very sane proposal, (the important points are stated in parens) because 'const' alone does not give any useful guarantees. The essential guarantee is that an immutable object attached to a const reference cannot be mutated through it. An issue is that fully 'const pure' methods would lose their guarantees when passed a mutable object, unless const member functions that mutate mutable members of the receiver object cannot be pure. This rule would disqualify the solution for what is discussed in the OP. Leaving the rule out would imply that the currently valid code transformation: int foo(const pure A){ } A a = ...; int x=foo(a), y=foo(a) => int x=foo(a), y=x; would become incorrect in the general case. The proposal trades off 'const' guarantees against mutable/immutable interoperability. I would be willing to take that.
May 16 2012
parent travert phare.normalesup.org (Christophe) writes:
Timon Gehr , dans le message (digitalmars.D:167544), a écrit :
 Leaving the rule out would imply that the currently valid code 
 transformation:
 
 int foo(const pure A){ }
 
 A a = ...;
 
 int x=foo(a), y=foo(a)
 =>
 int x=foo(a), y=x;
 
 would become incorrect in the general case. The proposal trades off 
 'const' guarantees against mutable/immutable interoperability. I would 
 be willing to take that.

The language could declare that the transformation is legal, and that the programmer using 'mutable' members is responsible for keeping the function logically const. -- Christophe
May 16 2012
prev sibling next sibling parent reply "Chris Cain" <clcain uncg.edu> writes:
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
parent reply Chad J <chadjoan __spam.is.bad__gmail.com> writes:
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
parent reply Chad J <chadjoan __spam.is.bad__gmail.com> writes:
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 15 2012
parent Chad J <chadjoan __spam.is.bad__gmail.com> writes:
On 05/15/2012 08:18 PM, Chris Cain wrote:
 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

I guess this matters for toHash... so now we not only want pure/const/nothrow methods, but there is this implication that we are desiring to mutate immutable things. If we really wanted to overcome this, we could have things that behave as if immutable, but carry mutable state internally. These would not be eligible for placement in ROM. The other thing that I'm wondering about is if there are use-cases /besides/ hashing. Are there any? If not, it might be worth special-casing somehow. It's a difficult route to travel though, because it's very difficult to know that there won't be any roadblocks besides caching in the future. Still, I suspect this is not well researched.
May 15 2012
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
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
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
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
prev sibling next sibling parent "Chris Cain" <clcain uncg.edu> writes:
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
May 15 2012
prev sibling parent "era scarecrow" <rtcvb32 yahoo.com> writes:
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.
 ... 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.


I think I have an answer. It came to me last night when I was going to sleep. Anyways, it may be a little verbose but I Hope I got it all right. Oddly enough it stays within D's own rules :) -- import std.stdio; import std.conv; struct CachedHash(T) { T element; uint hash; alias element this; this(T inVal) { element = inVal; } property uint toHash(){ if(!hash) { writeln("Hashing!"); hash = element.toHash(); } else writeln("From Cache!"); return hash; } } uint toHash(string x){ return 123456; //for example } class AString { string str; this(string s) { str = s; } const uint toHash(){ return 123456; } } uint calledHash(T)(T as){ return as.toHash(); } int main() { immutable char[] s = "this is a test!"; auto ch_s = CachedHash!string(s); //since s is inherently immutable.. writeln(s); writeln(s.toHash, "\n"); writeln(ch_s); writeln(ch_s.toHash); writeln(ch_s.toHash, "\n"); AString as = new AString(s); immutable AString ias = cast(immutable AString) new AString(s); auto as_s = CachedHash!AString(as); auto ias_s = CachedHash!(immutable AString)(ias); writeln(as.toHash, "\n"); writeln(as_s.toHash); writeln(as_s.toHash); writeln(ias_s.toHash); writeln(ias_s.toHash, "\n"); writeln(calledHash!AString(as_s)); //non-CachedHash aware simulate writeln(calledHash!(typeof(as_s))(as_s)); return 0; }
May 23 2012
prev sibling parent Marco Leise <Marco.Leise gmx.de> writes:
Am Mon, 14 May 2012 16:54:34 -0700
schrieb Walter Bright <newshound2 digitalmars.com>:

 On 5/14/2012 10:08 AM, Tove wrote:
 but c++ has the 'mutable' keyword as an easy escape route...

The existence of that capability means that 'const' in C++ cannot be meaningfully reasoned about.

class Foo { uint a, b; // can only call const pure nothrow members here: lazy uint somethingLazyInitialized = { return 2 * bar(); } uint bar() const pure nothrow safe { // complex calculation return a + b; } override uint toHash() const pure nothrow safe { // sets the field on the first use return somethingLazyInitialized; } } Internally the "lazy uint" consists of a function pointer and the uint. A lazy field acts like a read-only property. Whenever the field is read, code is generated that first checks, if the function pointer is not null. It then updates the uint with the return value of the function call and sets the function pointer to null, to indicate that the value is now initialized. An instance of Foo cannot be immutable (it makes no sense to ask for that with lazy initialization), but it can be const. This is a form of logical const that still allows reasoning about the code + compiler enforcement in contrast to the more flexible (in a positive and negative sense) C++ mutable. -- Marco
May 17 2012