June 23, 2007
I am afraid you will not like this idea, but not afraid enough. <g>

Why not using a single keyword "const_" adding a number 1, 2, 3 to represent :
invariant, final, readonlyview.
the higher the number the higher (the stronger) the const.
means : const_3 == invariant;
At least a mental help, IMO; somehow borrowed from Modula 2 processes.

Bjoern
June 23, 2007
Reiner Pope wrote:
> You mention functional programming a fair bit with respect to const, which is nice to hear. But nothing in the current const system allows you to declare a verifiably 'pure' function; can we expect some annotation for functions which says 'this function doesn't read/write any global variables?'
> 
>     int b
> 
>     int foo()
>     {
>         b++;
>         return b * 2;
>     }
> 
>     pure int square(int x)
>     {
>         return x * x;
>     }
> 
>     pure int baz()
>     {
>         return foo(); // fails: foo is not pure
>     }

I have two questions which relate to this.

1). Can invariant member functions change globals and / or static members? According to the doc I would think so, but the compiler crashes with this assert in that case: ito->isInvariant() on line 335 in file mtype.c.

If invariant member functions only guarantee invariance on the object / struct they are member of, is this the desired semantics? When looked at from the pov of an ADT, invariant member functions do not guarantee immutability of an object / struct, since their evaluation can rely on outside mutable state.

I'm not sure I can oversee the consequences, but what are the obstacles to defining the semantics of invariant member functions as pure functions?

2) What are the semantics of invariant free functions, or do they not have any special meaning? Again, can invariant be used to mean pure here? It would seem logical to me to apply invariance to the evaluation of functions as well as what they possibly refer to.
June 23, 2007
Lutger wrote:
> 1). Can invariant member functions change globals and / or static members? According to the doc I would think so, but the compiler crashes with this assert in that case: ito->isInvariant() on line 335 in file mtype.c.

File a bug!

-- 
Remove ".doesnotlike.spam" from the mail address.
June 23, 2007
I think that invariant-as-storage-class vs. const-as-storage-class is a false dichotomy, and also that they are incorrectly associated with the completely different type-constructors, const() and invariant().


As storage classes, const and invariant both mean "compile-time constant" -- their initializers must be evaluatable at compile time, and their data need not be stored in memory.

However, apparently, data referred to by const-as-storage-class could actually be liable to change (this is cited as the difference between const-as-storage-class and invariant-as-storage-class).

Yet this seems to clash with the idea of being initialized at compile time: in order for the data pointed to by const-as-storage-class to be changed by another reference, there must be a mutable pointer from which such a variable is initialized. Something like:

void main() {
   int a = 10;
   int* p = &a; // here's our mutable view
   const int* cp = &a; // here's our const-as-storage-class
}

At the moment, this doesn't compile, because &a is not evaluatable at compile time (even though the address of a is statically known). Assuming this is never supported (and I don't think that it should be), then the data pointed to by a const-storage-class variable can never be changed, so the data is really invariant. In that case, const-as-storage-class should be abolished, as it is identical to invariant-as-storage-class.[1]

---

I also think that the idea of a "compile time constant" is distinct from invariant-as-type-constructor. For the purposes of clarity, I think it would ideally be better to make this distinction clear, and give this storage class a different name (sorry about Yet Another Keyword). I think 'define' could make sense:

    define PI = 3.14;
    define LanguageName = "D Programming Language";

This also emphasises the similarity in meaning to #define.

Of course, typeof(LanguageName) == invariant(char[])

---

[1] Although the data pointed to by const-as-storage-class *is* invariant, the type system doesn't believe it. The following doesn't compile (correctly so, according to the specs)

    const char[] a = "abc";
    invariant char[] b = a;

but how can the data in 'a' possibly change?



   Reiner
June 23, 2007
Walter Bright wrote:
> Sean Kelly wrote:
>> However, my point was that to a programmer, optimization should be invisible.
> 
> I can't agree with that. The compiler and the programmer need to cooperate with each other in order to produce fast programms. If a programmer just throws the completed program "over the wall" to the optimizer and expects good results, he'll be sadly disappointed.

I suppose the issue is where the line should be drawn, and that line is constantly shifting.  Previously, "register" was actually a valuable tool in C/C++, now it's a pointless anachronism.  Techniques for producing optimal loops varied across compiler and platform--in some cases using pointers was better, in some cases array indexing was better--and now those are largely pointless as well.  But I think there are a few different factors to be considered here.  A significant one is that compilers have simply gotten better.  Another is that hardware has improved tremendously.  But it's interesting that you mentioned FORTRAN as being the language to beat for numerics performance because it's positively ancient and, to my knowledge, does not require the programmer to make any extra effort to produce such optimal code.  It's just a side-effect of the language design.

I'm sure you recognize this because D has 'foreach', which is as much an optimization tool as it is a programming aid.  The thing is, 'foreach' would be great from a programmer perspective even if it were the bane of compiler optimization.  It clarifies loop syntax, reduces errors, and adds a great deal of flexibility for iteration.  These are sort of features I want to see in D: those that make code more elegant and maintainable, and which produce optimal code simply as a side-effect of their design.  I suppose this is one reason I'm not quite ready to give up on "const by default" yet.  Converting code may be more difficult than with the current design, but the result seems to have the potential to be both cleaner and more suited to deep compiler optimization.

>> I can appreciate that 'invariant' may be of tremendous use to the compiler, but I balk at the notion of adding language features that seem largely intended as compiler "hints."
> 
> It's a lot more than that. First, there's the self-documenting aspect of it. Second, it opens the way for functional programming, which can be of huge importance.

Could you explain?  I've been trying to determine how I would write a library using these new features, and so far, 'invariant' simply seems more an obstacle to maintainability (because of the necessary code duplication), than it is an aid to programming.

>> Rather, it would be preferable if the language were structured in such a way as to make such hints unnecessary.
> 
> That would be preferable, but experience with such languages is that they are complete failures at producing fast code. Fast code comes from a language that enables the programmer to work with the optimizer to produce better code.

Really?  I thought that many functional programming languages were quite fast.  And FORTRAN seems a suitable example for an imperative language which is quite fast as well.  To my knowledge, none of these contain features specifically intended for optimizing code.

> Lots of people think that just 'cuz they write in C++, which has good optimizers available, they'll get fast code. That's not even close to being true.

Of course not.  Garbage in, garbage out.

>> To that end, and speaking as someone who isn't primarily involved in numerics programming, my impression of FORTRAN is that the language is syntactically suited to numerics programming, while C++ is not.  Even if C++ performed on par with FORTRAN for similar work (and Bjarne suggested last year that it could), I would likely still choose FORTRAN over C++ because the syntax seems so much more appealing for that kind of work.
> 
> I programmed for years in FORTRAN. The syntax is not appealing, in fact, it sucks. The reason it is suited for numerics programming is because FORTRAN arrays, by definition, cannot be aliased. This means that optimizers can go to town parallelizing array operations, which is a big, big deal for speed.
> 
> You can't do that with C/C++ because arrays can be aliased. I have a stack of papers 6" deep in the basement on trying to make C more suitable for numerics, and it's mostly about, you guessed it, fixing the alias problem. They failed.

That's the big secret?  Weird.  For some reason I assumed it was a bit less specific.

> There are some template libraries for C++ which parallelize array operations and can finally approach FORTRAN. But they are horrifically kludgy and complicated. No thanks. But the effort that has gone into them is astonishing, and is indicative of how severe the problem is.

Yup.  And I agree that this isn't the proper route to follow.  Heck, it's one reason I've lost interest in C++.

>>> I'm less sure about that. I think we're all so used to C++ and its mushy concept of const that we don't know yet what will emerge from the use of invariant. I do know, however, that those who want to do advanced array optimizations are going to want to be using invariant function parameters.
>>
>> You may be right, and I'm certainly willing to give it a try.  This is simply my initial reaction to the new design, and I wanted to voice it before becoming placated by experience.  My gut feeling is that a better design is possible, and I'm not yet ready to close the door on alternatives.
> 
> Andrei, I and Bartosz have each expended probably a hundred hours trying to figure this out, and we've tried a lot of designs. If there is a better design, it's not like we haven't tried. I wish to reiterate that const designs in other languages like C++ (and to some extent Java) utterly fail at the objectives we set for const in D. Furthermore, Andrei is familiar with the many research papers on the topic, which were of invaluable help. D's const system is more ambitious than any of them.

I really do appreciate the effort which you all have made to find a solid design, and perhaps I simply don't have enough experience with it to feel comfortable with it yet.  As you've no doubt noticed, my issue is with 'invariant' from a conceptual and a code maintenance standpoint.  On the one hand we have 'const' and on the other we have 'really really const'.  It just sticks in my craw that we need two separate keywords for what seem to be nearly identical properties, and that I may have to write separate code to suit each, etc, not to mention trying to explain all of it to a new programmer.

>> The compiler can inspect the code however, and a global const is as good as an invariant for optimization (as far as I know).  As for the rest, I think the majority of remaining cases aren't ones where 'invariant' would apply anyway: dynamic buffers whose contents are guaranteed not to change either in word or by design, etc.
> 
> But they do apply - that's the whole array optimization thing. You're not just going to have global arrays.

Perhaps I misunderstood the "must be known at compile-time" clause.  Can 'invariant' apply to dynamic arrays that will remain unchanged once initialized?  In my work I use almost no static data--it's all generated on the fly or loaded from some data source.  Will 'invariant' help to make my code more optimal?


Sean
June 23, 2007
Daniel Keep wrote:
> 
> Sean Kelly wrote:
>> Daniel Keep wrote:
>>> final int x;        typeof(x) == int;
>>> const int y;        typeof(y) == const int;
>>> final const int z;    typeof(z) == final const int;
>> Hm.  Just to clarify, we both agree that the value of a final integer
>> (ie. case 1 above) is effectively constant, correct?
> 
> Indeed.
> 
>>> "Wait; why is final part of the last type, but not the first?"  And what
>>> does this mean if you want a class member with final-style binding, but
>>> (final const) type semantics?
>>>
>>> final (final const(int*)) foo;
>>>
>>> As opposed to
>>>
>>> final invariant(int*) foo;
>> Perhaps I'm missing something, but I would rewrite this as:
>>
>> final const int* foo;
>>
>> Thus foo cannot be reassigned once set and the data foo refers to may
>> not be changed through foo.  This is a slightly weaker guarantee than:
>>
>> final invariant int* foo;
>>
>> Which says that the data foo refers to is immutable, but I am skeptical
>> that this guarantee actually matters much to users.
>>
>> Or am I completely misunderstanding?  And why the parenthesis in the
>> second declaration?
> 
> (When I wrote the below, I missed precisely what you were saying: the
> problem with writing it at "final const int*" is that you've got both
> final and const as a storage class; I assumed you were using them as a
> type constructor.)
> 
> Ok, let's try this instead: in the current system, we have
> 
> class Foo
> {
>     final invariant(FunkyType) funky;
> 
>     this(bool superFunky)
>     {
>         if( superFunky )
>             funky = cast(invariant) new FunkyType;
>         else
>             funky = some_global_invariant_funky;
>     }
> }
> 
> In this case, we have a final storage (so we can assign the value during
> the ctor), and an invariant(FunkyType) value type.
> 
> Under your proposal, invariant is replaced with (final const); if we
> want the above, it'd become:
> 
>     final final const(FunkyType) funky;

Under my proposal, this would be:

    final const FunkyType funky;

> But how does the compiler tell that second final is storage class or
> part of the type constructor?  Remember, we could be dealing with a
> template that's just shoving "final" out the front of things, so we
> can't assume two finals ==> one is a type constructor.  So we'd probably
> have to do this:
> 
>     final (final const(FunkyType)) funky;

Hm.  I'm afraid I don't understand all this talk about type constructor vs. storage class.  What I get from the above is that:

    final const FunkyType funky;

represents a fixed reference to an instance of FunkyType, which cannot be changed through the reference.

> We can't use *just* "final const FunkyType funky" because then we'd have
> an "invariant" storage class: which means the initialiser would have to
> be a compile-time constant.

Why would it have to be an "invariant" storage class?  It seems I'm missing something fundamental here.

> Incidentally, the above is also probably
> equivalent to:
> 
>     final(final const(FunkyType)) funky;
> 
> Which really doesn't make any sense anyway...

>>> I think the thing here is that you're shifting the complexity from
>>> invariant into final; instead of invariant meaning two different things
>>> with two very different appearances, you've got final meaning two
>>> slightly different things with almost identical looks.
>> I only see final meaning one thing: that the associated value may not be
>> reassigned.  For concrete types like integers this is effectively the
>> same as const, but as Walter said, the integer would be addressable when
>> final but not when const.  Perhaps this is the source of confusion?
>>
> 
> Yes, but it *also* means "can assign to in a ctor".

Right.  That's compatible with what I said.  It can be assigned in a ctor but not *reassigned* once assigned.  Just like 'const' in D 1.0.

> Like Lars said, I do think that if there's a way to simplify or
> consolidate this, then we should take it.  That said, I can see a use
> for each of the various cases the new system allows for, and I don't
> want to see any of them cut off in the name of "well *I'm* not going to
> use it, and I don't like that keyword, so it has to go!"[1]. :)

I agree.


Sean
June 23, 2007
On Thu, 21 Jun 2007 10:36:12 +0400, Walter Bright <newshound1@digitalmars.com> wrote:

> http://www.digitalmars.com/d/const.html

While reading this topic (specialy discussion between Sean and Walter) I've understood what confusing me in current const/invariant/final design:

Keyword 'const' for value-types means immutability. For example, as I think, the following declarations are the same:

const int i = 0;
invariant int j = 0;

because both i and j cannot be changed or reassinged, both i and j must be initialized at the compile time. DMD 2.000 even doesn't allow getting address of i.

But for reference types and for pointers 'const' means 'constant view' to other data. That dualism little confuses me.

My propose is dissable use 'const' keyword for value-type and allow 'const' only for 'constant view'. Let value-type constant be defined by 'invariant'.

-- 
Regards,
Yauheni Akhotnikau
June 23, 2007
Reiner Pope wrote:
> You mention functional programming a fair bit with respect to const, which is nice to hear. But nothing in the current const system allows you to declare a verifiably 'pure' function; can we expect some annotation for functions which says 'this function doesn't read/write any global variables?'

You're right, we're moving in the right direction, but we're not there yet.
June 23, 2007
Sean Kelly wrote:
> Walter Bright wrote:
>> Sean Kelly wrote:
>>> However, my point was that to a programmer, optimization should be invisible.
>>
>> I can't agree with that. The compiler and the programmer need to cooperate with each other in order to produce fast programms. If a programmer just throws the completed program "over the wall" to the optimizer and expects good results, he'll be sadly disappointed.
> 
> I suppose the issue is where the line should be drawn, and that line is constantly shifting.  Previously, "register" was actually a valuable tool in C/C++, now it's a pointless anachronism.  Techniques for producing optimal loops varied across compiler and platform--in some cases using pointers was better, in some cases array indexing was better--and now those are largely pointless as well.  But I think there are a few different factors to be considered here.  A significant one is that compilers have simply gotten better.  Another is that hardware has improved tremendously.  But it's interesting that you mentioned FORTRAN as being the language to beat for numerics performance because it's positively ancient and, to my knowledge, does not require the programmer to make any extra effort to produce such optimal code.  It's just a side-effect of the language design.

It's a side effect of:

1) no pointers
2) no aliasing

It's not a result of genius on the part of FORTRAN's designers. And it's only more efficient for certain types of code - for everything else, it sucks.


> I'm sure you recognize this because D has 'foreach', which is as much an optimization tool as it is a programming aid.  The thing is, 'foreach' would be great from a programmer perspective even if it were the bane of compiler optimization.  It clarifies loop syntax, reduces errors, and adds a great deal of flexibility for iteration.  These are sort of features I want to see in D: those that make code more elegant and maintainable, and which produce optimal code simply as a side-effect of their design.  I suppose this is one reason I'm not quite ready to give up on "const by default" yet.  Converting code may be more difficult than with the current design, but the result seems to have the potential to be both cleaner and more suited to deep compiler optimization.
> 
>>> I can appreciate that 'invariant' may be of tremendous use to the compiler, but I balk at the notion of adding language features that seem largely intended as compiler "hints."
>>
>> It's a lot more than that. First, there's the self-documenting aspect of it. Second, it opens the way for functional programming, which can be of huge importance.
> 
> Could you explain?  I've been trying to determine how I would write a library using these new features, and so far, 'invariant' simply seems more an obstacle to maintainability (because of the necessary code duplication), than it is an aid to programming.

For one thing, if you have a reference to an invariant in a multithreaded program, you won't have to worry about putting a lock around it in order to read it.

For the FP aspect, it means that if a function only depends on its parameters, and its parameters are passed by value, the compiler has enough information about it to reorder when the function is executed, which means it can parallelize it.

If the compiler cannot determine the side effects, it is forced to execute the functions serially.

>> That would be preferable, but experience with such languages is that they are complete failures at producing fast code. Fast code comes from a language that enables the programmer to work with the optimizer to produce better code.
> 
> Really?  I thought that many functional programming languages were quite fast.

I didn't mean FP languages, I meant languages where one is supposed to ignore optimization and let the compiler do it all.

> And FORTRAN seems a suitable example for an imperative language which is quite fast as well.  To my knowledge, none of these contain features specifically intended for optimizing code.

It's an unintended side effect of FORTRAN's primitive semantics.


>> I programmed for years in FORTRAN. The syntax is not appealing, in fact, it sucks. The reason it is suited for numerics programming is because FORTRAN arrays, by definition, cannot be aliased. This means that optimizers can go to town parallelizing array operations, which is a big, big deal for speed.
>>
>> You can't do that with C/C++ because arrays can be aliased. I have a stack of papers 6" deep in the basement on trying to make C more suitable for numerics, and it's mostly about, you guessed it, fixing the alias problem. They failed.
> 
> That's the big secret?  Weird.  For some reason I assumed it was a bit less specific.

That's the big secret.


> I really do appreciate the effort which you all have made to find a solid design, and perhaps I simply don't have enough experience with it to feel comfortable with it yet.  As you've no doubt noticed, my issue is with 'invariant' from a conceptual and a code maintenance standpoint.  On the one hand we have 'const' and on the other we have 'really really const'.  It just sticks in my craw that we need two separate keywords for what seem to be nearly identical properties, and that I may have to write separate code to suit each, etc, not to mention trying to explain all of it to a new programmer.

It's like int and uint - most of the time, they behave the same. But when they're different, they're very different.


> Perhaps I misunderstood the "must be known at compile-time" clause.

That's only when you use invariant as a storage class. It doesn't apply when you use it as a type constructor.

>  Can 'invariant' apply to dynamic arrays that will remain unchanged once initialized?

Yes.

  In my work I use almost no static data--it's all generated
> on the fly or loaded from some data source.  Will 'invariant' help to make my code more optimal?

Not with the current compiler, because the back end hasn't been modified to take advantage of it. But the potential is there.
June 23, 2007
BLS wrote:
> I am afraid you will not like this idea, but not afraid enough. <g>
> 
> Why not using a single keyword "const_" adding a number 1, 2, 3 to represent :
> invariant, final, readonlyview.
> the higher the number the higher (the stronger) the const.
> means : const_3 == invariant;
> At least a mental help, IMO; somehow borrowed from Modula 2 processes.

You're right, I don't like the idea <g>.