Thread overview
Const Variables
Apr 03, 2022
Salih Dincer
Apr 03, 2022
Paul Backus
Apr 03, 2022
Salih Dincer
Apr 03, 2022
Ali Çehreli
Apr 04, 2022
H. S. Teoh
Apr 04, 2022
Ali Çehreli
Apr 04, 2022
H. S. Teoh
Apr 04, 2022
Ali Çehreli
April 03, 2022

Hi all,

Do you have a good example of how const variables actually work?

So I'm not talking about system resources and programming errors. I want to say it's good. Because if everything works without it, why does it exist?

Thanks, SDB@79

April 03, 2022

On Sunday, 3 April 2022 at 13:50:28 UTC, Salih Dincer wrote:

>

Hi all,

Do you have a good example of how const variables actually work?

So I'm not talking about system resources and programming errors. I want to say it's good. Because if everything works without it, why does it exist?

This is covered in the const FAQ:

https://dlang.org/articles/const-faq.html

April 03, 2022

On Sunday, 3 April 2022 at 14:29:22 UTC, Paul Backus wrote:

>

On Sunday, 3 April 2022 at 13:50:28 UTC, Salih Dincer wrote:

>

Hi all,

Do you have a good example of how const variables actually work?

So I'm not talking about system resources and programming errors. I want to say it's good. Because if everything works without it, why does it exist?

This is covered in the const FAQ:

https://dlang.org/articles/const-faq.html

The programs I write are not multithreaded. Also, I don't work as a team. Thanks for the link but there is only the following example:

struct Foo
{
    mutable int len;
    mutable bool len_done;
    const char* str;
    int length()
    {
        if (!len_done)
        {
            len = strlen(str);
            len_done = true;
        }
        return len;
    }
    this(char* str) { this.str = str; }
}
const Foo f = Foo("hello");
bar(f.length);

Moreover, I did not understand anything from the example in the article :)

SDB@79

April 03, 2022
On 4/3/22 11:24, Salih Dincer wrote:
> On Sunday, 3 April 2022 at 14:29:22 UTC, Paul Backus wrote:
>> On Sunday, 3 April 2022 at 13:50:28 UTC, Salih Dincer wrote:
>>> Hi all,
>>>
>>> Do you have a good example of how const variables actually work?

You are right that const is not absolutely necessary. However, it is such a useful concept that some programming languages don't allow mutation at all.

Immutability is seen to be so valuable that some people argue that the default in D should be immutable. (Rust is just one example where the default is const. In that language, you have to use 'mut' (presumably short for "mutable") for variables that can be mutated.)

>> This is covered in the const FAQ:
>>
>> https://dlang.org/articles/const-faq.html
>
> The programs I write are not multithreaded. Also, I don't work as a
> team.

So you agree with that article. :) The rest of us who work with multi-threaded programs or with other developers agree with it as well. ;)

> Thanks for the link but there is only the following example:
>
> ```d
> struct Foo
> {
>      mutable int len;

Wow! That article must be from the early days of D. There is no 'mutable' in D (any more?).

>      mutable bool len_done;
>      const char* str;
>      int length()
>      {
>          if (!len_done)
>          {
>              len = strlen(str);
>              len_done = true;

That example is the concept of logical constness, where the object computes some value as neeeded and caches the result for later use. The member 'len' (and len_done) is modified even if the 'f' object below is const. However, the example doesn't fit today's D because the function should have been marked as 'const' as well:

int length() const {
  // ...
}

In any case, there is no logical constness in D. (However, one can play tricks like using casts. (Which may be undefined behavior, etc. etc.))

>          }
>          return len;
>      }
>      this(char* str) { this.str = str; }
> }
> const Foo f = Foo("hello");
> bar(f.length);

So, there is the 'const' object f and calling its length() member function, which mutates two members of the object. As those mutations do not change the value of the object, the object is considered still the same. In other words, caching something about the object is considered to not change that object. That's what is meant with logical const: There has been changes but they did not change the state of the object.

Ali

April 03, 2022
On Sun, Apr 03, 2022 at 03:42:15PM -0700, Ali Çehreli via Digitalmars-d-learn wrote: [...]
> In any case, there is no logical constness in D. (However, one can
> play tricks like using casts. (Which may be undefined behavior, etc.
> etc.))
[...]

A thought occurred to me.  What is logical const, and how can we make it useful?  Useful meaning, it's not just a convention (like in C++) with no hard guarantees and no verifiability.

Currently, D has physical const, so `const` cannot be used for things like caching, lazily-initialized objects, and other things that are logically const but not physically const because mutation is involved. But what really is physical const?  It's essentially a contract, guaranteed by the language and statically verified by the compiler, that the physical data has not changed.  IOW, it's a contract stating that the data physically stored before operation X is identical to the data physically stored after operation X, and that it has not changed in the meantime.

Now can we extend this concept to logical const?  Perhaps in this way: instead of guaranteeing the physical data is unchanged, what about a contract that guarantees that *externally-observable behaviour* of the object is unchanged?  Meaning, the object may actually have changed internally, but as long as this cannot be observed externally (and this includes *future* behaviour of the object -- so we're not just slipping internal state changes under the carpet that may later alter the object's behaviour), to the outside world it behaves as though it were actually const.

In the case of lazy initialization, for example, it ought to be provable that the value of the object, as observed by the outside world, cannot change. The initial state where the value is uninitialized is not observed by the outside world; the only observable value only appears after initialization, and does not change henceforth.

Similarly for a cached value: it ought to be provable that the first computation of value is the *only* value that can be externally observed; after the initial computation, the value will never change again.

In some sense, this is like an extension of ctors initializing immutable values. The compiler tracks whether the variable has been initialized yet, and inside the ctor you can assign to immutable because the uninitialized value is not observable from outside before that. Once assigned, the compiler enforces no subsequent changes.

It's also like D's extension of purity, where impure operations are allowed as long as the outside world cannot tell the difference.

There should be some way of implementing logical const along these lines that still provides guarantees whilst relaxing the physical immutability rule.


T

-- 
It only takes one twig to burn down a forest.
April 04, 2022
On 4/3/22 17:42, H. S. Teoh wrote:

> In some sense, this is like an extension of ctors initializing immutable
> values. The compiler tracks whether the variable has been initialized
> yet, and inside the ctor you can assign to immutable because the
> uninitialized value is not observable from outside before that. Once
> assigned, the compiler enforces no subsequent changes.

These ideas make sense to me.

I remember reading somewhat similar requests in the past: Assign to a variable freely but at some point say "no more mutation to this variable".

Ali

April 04, 2022
On Mon, Apr 04, 2022 at 11:20:31AM -0700, Ali Çehreli via Digitalmars-d-learn wrote:
> On 4/3/22 17:42, H. S. Teoh wrote:
> 
> > In some sense, this is like an extension of ctors initializing immutable values. The compiler tracks whether the variable has been initialized yet, and inside the ctor you can assign to immutable because the uninitialized value is not observable from outside before that. Once assigned, the compiler enforces no subsequent changes.
> 
> These ideas make sense to me.
> 
> I remember reading somewhat similar requests in the past: Assign to a variable freely but at some point say "no more mutation to this variable".
[...]

It's not just "assign freely"; it's "assign once". Or rather, "assign before the outside world sees it".

In fact, now that I think of it, I wonder if this could actually be implementable in the current language. Here's a first stab at it:

	struct LazyValue(T) {
		private class Impl
		{
			immutable T value;
			this(immutable T v) { value = v; }
		}
		private Impl impl;
		private T delegate() pure generate;

		this(T delegate() pure _generate)
		{
			generate = _generate;
		}

		immutable(T) get() {
			if (impl is null)
				impl = new Impl(generate());
			return impl.value;
		}
		alias get this;
	}

	void main() {
		import std;
		auto lazyInt = LazyValue!int({ return 123; });
		writeln(lazyInt.get);
	}

Currently generate() has to be pure, which limits the utility somewhat
(you couldn't load the value from a file, say).  But in principle, the
approach seems to work.


T

-- 
Obviously, some things aren't very obvious.
April 04, 2022
On 4/4/22 13:37, H. S. Teoh wrote:

>> I remember reading somewhat similar requests in the past: Assign to a
>> variable freely but at some point say "no more mutation to this
>> variable".
> [...]
>
> It's not just "assign freely"; it's "assign once". Or rather, "assign
> before the outside world sees it".

I understand. I just remembered somewhat related other requests.

> In fact, now that I think of it, I wonder if this could actually be
> implementable in the current language.

I am not surprised. :)

Ali