October 09, 2013
On Wednesday, 9 October 2013 at 04:41:35 UTC, Ali Çehreli wrote:
> On 10/08/2013 03:03 PM, qznc wrote:
>
> > On Wednesday, 2 October 2013 at 13:09:34 UTC, Daniel Davidson
> wrote:
> >> 1. If a variable is never mutated, make it const, not
> immutable.
> >> 2. Make the parameter reference to immutable if that is how
> you will
> >> use it anyway. It is fine to ask a favor from the caller.
> >> ...
> >
> > I think guideline 1 should be about "arguments" not
> "variables".
> > Functions should take const arguments, so they can be called
> with
> > mutable and immutable values.
>
> As demonstrated in the presentation, const erases the actual type. So, the function must take a copy if the argument is to be used in an immutable context.
>
> Instead, if the immutable appears on the interface, that copy will be avoided.
>

True. But what does it mean to be "used in an immutable context". In your example you pass a variable to a function `void usefulFunction(string s)`. The problem with transitivity is once you start making rules regarding it, those rules must also be transitive (or turtles all the way down as you say). The point is, the desire in the example wants to use the guideline only at the top level and break it at the next level. If usefulFunction were following the guideline it would have a signature `void usefulFunction(const(char)[] s)` instead.

If the guideline were: "if a function parameter is not mutated, make it const" and it were applied that way everywhere, the problem you suggest would not be hit. But we can't go changing any `standardFunction(string s)` to `standardFunction(const(char)[] s)`. Using const on parameters seems to be the prevalent guideline - even though passing strings breaks it.

In regards to parameters, when, if ever, is there benefit to immutable over const (if you assume all called functions from that function have const parameters/signatures). The only reason I can think is if a *member* function wants to hold onto a reference or copy for future use and the designer wants to guarantee that member is truly immutable. An example would be a struct that initializes with an immutable(T) that the struct wants to then use throughout its life.

struct S { this(ref immutable(T) t) { _t = t; }  immutable(T) _t; }

This is all very confusing and to me unsettled. I've brought up questions like this many times and have yet to hear Walter or Andrei weigh in. Either we (you and I) are missing something and making things much more difficult than necessary or it is just difficult. I think a third likely possibility is that composition in the face of structs with mutable aliasing is not heavily used by them.

> > I would rephrase the second guideline as: "Never dup or idup
> an argument
> > to make it mutable or immutable, but require the caller to do
> this
> > (might be able to avoid it)".
>
> Agreed. But it means you agree with me that immutable should indeed appear on function signatures as needed.
>

I don't really agree. Naturally, if const were used all the way down there would be no need for copies. It is the introduction of immutable (via string in your example) that breaks the rule and causes the pain. But then if the rule were adopted globally - immutable is never used on parameters in a signature, only const is used, when would you start seeing any benefit to the immutable keyword?

> > For more guidelines:
> >
> > 3. Return value, which are freshly created, should never be
> const. They
> > should be mutable or immutable.
>
> Agreed. I say that it should be mutable if the function is pure, as such a return values is automatically convertible to immutable.
>
> > Basically, I cannot come up with a case, where const would be
> preferable
> > to mutable and immutable. Maybe someone is able to find a
> good example?
>

I don't know if these are good examples, but they are examples:

From zip.d:
        const(void)[] compress(const(void)[] buf)

From vibe:
 	const(Json) opIndex(string key) const {


[snip]

> > 4. Data structures should not restrict themselves to be
> mutable, const,
> > or immutable.
>
> What is the template of a struct that can be used as such? Providing simple values seems to be insufficient:
>
> struct MyInt
> {
>     int i;
>     private int[] history;
> }
>
> What else should the struct above have to make it usable as mutable, const, immutable?
>

I'm confused by the term "usable as mutable, const, immutable"? Isn't it true that types are not mutable, const, immutable in their definition but only in their context?

[snip]

> We have to nail this down already. We have this great addition of immutable in the language but we either don't know how to use it or the language is lacking hopefully trivial things to make it work.
>

Amen, brother!

Either it is a great addition and usable, or it is not so great an addition. At times I find myself questioning even the basic benefit of immutable in the context of sharing. I've heard that immutable is great because it means the data can be shared across threads and you have confidence it will never change. This sounds good. But then you have to choose signatures:

void foo(const(MutableType) mt);
void foo(immutable(MutableType) mt);

Naturally the inclination is to choose the second as it is a stronger guarantee that no threads are changing the data. Cool. But wait, the first one still probably requires the same guarantee, even if it does not state it. If in the const case another thread changes the state of mt during the function foo then it will fail. foo actually requires that mt not be change by other threads during its operation - that is the only sane way of using the data in the function.

Take, for example, LinearCongruentialEngine from random.d. It has a function:

    bool opEquals(ref const LinearCongruentialEngine rhs) const

Why is it using const here instead of immutable? Does it not care about other threads? No - it just is not smart enough to deal with it. It assumes other threads won't be changing it or if they are caveat developer. So when do you use immutable as a signal that not only will this function not change it, no thread will change it either? Probably when you know you have to deal with threading. But that then would be coupling two maybe orthogonal decisions - the threading of a program and the signature of functions.

Where is Scott Myers when you need him :-)


> Ali
October 09, 2013
On Wednesday, 9 October 2013 at 15:50:55 UTC, Daniel Davidson wrote:
> void foo(const(MutableType) mt);
> void foo(immutable(MutableType) mt);
>
> Naturally the inclination is to choose the second as it is a stronger guarantee that no threads are changing the data. Cool. But wait, the first one still probably requires the same guarantee, even if it does not state it. If in the const case another thread changes the state of mt during the function foo then it will fail. foo actually requires that mt not be change by other threads during its operation - that is the only sane way of using the data in the function.

Why "the first one still probably requires the same guarantee"?
Why would foo fail if "in the const case  another thread changes the state of mt"?
If foo is not thread-safe, then it should require immutable.

> Take, for example, LinearCongruentialEngine from random.d. It has a function:
>
>     bool opEquals(ref const LinearCongruentialEngine rhs) const
>
> Why is it using const here instead of immutable? Does it not care about other threads? No - it just is not smart enough to deal with it. It assumes other threads won't be changing it or if they are caveat developer. So when do you use immutable as a signal that not only will this function not change it, no thread will change it either? Probably when you know you have to deal with threading. But that then would be coupling two maybe orthogonal decisions - the threading of a program and the signature of functions.

Hm, that actually looks like a bug. If LinearCongruentialEngine is instantiated with a UIntType larger than size_t, it is not thread-safe, but this seems to be claimed in the section header.

Why are those two decisions orthogonal? The signature of a function is a contract between caller and callee. If an argument is const, it means the callee says he can handle others changing the state concurrently.

October 09, 2013
On Wednesday, 9 October 2013 at 15:50:55 UTC, Daniel Davidson wrote:
>> > I would rephrase the second guideline as: "Never dup or idup
>> an argument
>> > to make it mutable or immutable, but require the caller to do
>> this
>> > (might be able to avoid it)".
>>
>> Agreed. But it means you agree with me that immutable should indeed appear on function signatures as needed.
>
> I don't really agree. Naturally, if const were used all the way down there would be no need for copies. It is the introduction of immutable (via string in your example) that breaks the rule and causes the pain. But then if the rule were adopted globally - immutable is never used on parameters in a signature, only const is used, when would you start seeing any benefit to the immutable keyword?

The benefit of immutable is that nobody else can change it. Functions that would be non-thread-safe with const arguments are thread-safe with immutable arguments.

That relationship with thread-safety almost seems to be a good guideline.

5. If a function is not thread-safe due to const arguments, consider making it immutable. Otherwise you must explicitly document the lack of thread-safety.


>> > For more guidelines:
>> >
>> > 3. Return value, which are freshly created, should never be
>> const. They
>> > should be mutable or immutable.
>>
>> Agreed. I say that it should be mutable if the function is pure, as such a return values is automatically convertible to immutable.
>>
>> > Basically, I cannot come up with a case, where const would be
>> preferable
>> > to mutable and immutable. Maybe someone is able to find a
>> good example?
>>
>
> I don't know if these are good examples, but they are examples:
>
> From zip.d:
>         const(void)[] compress(const(void)[] buf)

Looking it up (in zlib.d actually) it could just as well return an immutable array, since it is freshly allocated.

> From vibe:
>  	const(Json) opIndex(string key) const {

Not sure where exactly this comes from. Seems to be some kind of map? In this case the value is not "freshly created", so the guideline does not apply.

> I'm confused by the term "usable as mutable, const, immutable"? Isn't it true that types are not mutable, const, immutable in their definition but only in their context?

MyInt is a type, probably mutable.
immutable(MyInt) is also a type, but immutable.
const(MyInt) is also a type, but const.

These are example of immutable and const being used as type constructors. They create a new type from an existing one (MyInt).
October 10, 2013
On Wednesday, 9 October 2013 at 23:05:27 UTC, qznc wrote:
> On Wednesday, 9 October 2013 at 15:50:55 UTC, Daniel Davidson wrote:
>> void foo(const(MutableType) mt);
>> void foo(immutable(MutableType) mt);
>>
>> Naturally the inclination is to choose the second as it is a stronger guarantee that no threads are changing the data. Cool. But wait, the first one still probably requires the same guarantee, even if it does not state it. If in the const case another thread changes the state of mt during the function foo then it will fail. foo actually requires that mt not be change by other threads during its operation - that is the only sane way of using the data in the function.
>
> Why "the first one still probably requires the same guarantee"?
> Why would foo fail if "in the const case  another thread changes the state of mt"?
> If foo is not thread-safe, then it should require immutable.
>
>> Take, for example, LinearCongruentialEngine from random.d. It has a function:
>>
>>    bool opEquals(ref const LinearCongruentialEngine rhs) const
>>
>> Why is it using const here instead of immutable? Does it not care about other threads? No - it just is not smart enough to deal with it. It assumes other threads won't be changing it or if they are caveat developer. So when do you use immutable as a signal that not only will this function not change it, no thread will change it either? Probably when you know you have to deal with threading. But that then would be coupling two maybe orthogonal decisions - the threading of a program and the signature of functions.
>
> Hm, that actually looks like a bug. If LinearCongruentialEngine is instantiated with a UIntType larger than size_t, it is not thread-safe, but this seems to be claimed in the section header.
>
> Why are those two decisions orthogonal? The signature of a function is a contract between caller and callee. If an argument is const, it means the callee says he can handle others changing the state concurrently.

I don't think that is how people interpret and use const. The general guideline is, for function parameters use const because it is most accepting - it takes mutable, immutable and const. If there are multiple threads developer beware.

Threading is or can be somewhat orthogonal to function signatures simply because you can write a whole lot of code with the const guideline above. Then with proper serialized access use that thread unaware code in multi-threaded context.
October 10, 2013
On 10/10/13 1:05 , qznc wrote:
Very interesting discussion!

> contract between caller and callee. If an argument is const, it means
> the callee says he can handle others changing the state concurrently.
i think what the usual understanding of const for an argument to callee is, what is written at http://dlang.org/const3.html. that means for me, that callee promises not to change the data itself. perhaps a bettr description would be readonly.

usually you would think that no one else should change the data while callee runs. but at least with c++ i could imagine running callee in a thread with a reference to a const thing which changes underneath and while callee is running.

October 10, 2013
On Wednesday, 9 October 2013 at 04:31:55 UTC, Ali Çehreli wrote:
> On 10/08/2013 03:12 PM, qznc wrote:
> > On Monday, 7 October 2013 at 17:57:11 UTC, Ali Çehreli wrote:
> >> To look at just one usage example, the following line
> carries two
> >> requirements:
> >>
> >>     auto a = T();
> >>     immutable b = a;
> >>
> >> 1) b will be an immutable copy of a.
> >>
> >> 2) T will always be usable as in that fashion.
> >>
> >> If T appears on an API, it is the responibility of the user
> to ensure
> >> whether they are allowed to treat T in that way. Otherwise,
> they risk
> >> maintainability if the module decides to change T in any way
> that fits
> >> the module's needs. If they have not yet advertised that T
> can be used
> >> as immutable, it should not be.
> >
> > I do not agree with you, that the user has the responsibility.
>
> I have difficulty agreeing with myself as well. :) However, the power of immutable makes me think so. Interestingly, once a user creates an immutable variable of a type, that type must support that use.
>
> > Rather I
> > think the provider of T has the responsibility to maintain
> backwards
> > compatibility.
>
> Agreed but I don't know how. Here is challenge: Let's start with the following program:
>
> // Library type
> struct MyInt
> {
>     int i;
> }
>
> void main()
> {
>     // User code
>     auto a = MyInt(1);
>     immutable b = a;
> }
>
> Let's assume that the library adds a private dynamic array of ints to that type:
>
> // Library type
> struct MyInt
> {
>     int i;
>     private int[] history;    // <-- Added
> }
>
> void main()
> {
>     // User code
>     auto a = MyInt(1);
>     immutable b = a;          // <-- Existing code breaks
> }
>
> Error: cannot implicitly convert expression (a) of type MyInt to immutable(MyInt)

Maybe the fact that D allows this implicit copy to immutable is the problem? If one could require the use of a specific function, this function could be overridden with working behavior. The following code works.

import std.exception: assumeUnique;

struct MyInt
{
  int i;
  private int[] history;    // <-- Added
}

// idup for creating immutable MyInts
immutable(MyInt) idup(const MyInt mi) pure nothrow @trusted {
  MyInt cpy = MyInt(mi.i);
  return cast(immutable) cpy;
}

// special version for performance
immutable(MyInt) idup(immutable MyInt mi) pure nothrow @trusted {
  return mi;
}

unittest {
  auto a = MyInt(1);
  immutable b = a.idup;     // <-- Code does not break
}

D could either remove the implicit-copy-to-immutable or provide a special copy-constructor for immutable structs.
October 10, 2013
On Thursday, 10 October 2013 at 18:39:32 UTC, Christian Köstlin wrote:
> On 10/10/13 1:05 , qznc wrote:
> Very interesting discussion!
>
> > contract between caller and callee. If an argument is const,
> it means
> > the callee says he can handle others changing the state
> concurrently.
> i think what the usual understanding of const for an argument to callee is, what is written at http://dlang.org/const3.html. that means for me, that callee promises not to change the data itself. perhaps a bettr description would be readonly.

The linked page clearly says "It may, however, be changed by another reference to that same data."

> usually you would think that no one else should change the data while callee runs. but at least with c++ i could imagine running callee in a thread with a reference to a const thing which changes underneath and while callee is running.

A const argument gives information to both sides. For the caller it means callee does not modify the data. For the callee it means somebody else (another thread) may modify the data at any time.

An immutable argument means the same for the caller, but gives an additional guarantee to the callee that nobody modifies the data.
October 10, 2013
On Thursday, 10 October 2013 at 23:06:23 UTC, qznc wrote:
> Maybe the fact that D allows this implicit copy to immutable is the problem? If one could require the use of a specific function, this function could be overridden with working behavior. The following code works.
>

Yes - the issue arises because the language has different behavior for structs with mutable aliasing and structs without. For structs without mutable aliasing a copy is safe so crossing bounds between any of (mutable, const, immutable) is easily achieved.

> import std.exception: assumeUnique;
>
> struct MyInt
> {
>   int i;
>   private int[] history;    // <-- Added
> }
>
> // idup for creating immutable MyInts
> immutable(MyInt) idup(const MyInt mi) pure nothrow @trusted {
>   MyInt cpy = MyInt(mi.i);
>   return cast(immutable) cpy;
> }
>
> // special version for performance
> immutable(MyInt) idup(immutable MyInt mi) pure nothrow @trusted {
>   return mi;
> }
>
> unittest {
>   auto a = MyInt(1);
>   immutable b = a.idup;     // <-- Code does not break
> }
>
> D could either remove the implicit-copy-to-immutable or provide a special copy-constructor for immutable structs.

See this discussion: http://forum.dlang.org/thread/fmhkvogowjlduqercsjn@forum.dlang.org
October 10, 2013
On 09/10/13 17:50, Daniel Davidson wrote:
> Take, for example, LinearCongruentialEngine from random.d. It has a function:
>
>      bool opEquals(ref const LinearCongruentialEngine rhs) const
>
> Why is it using const here instead of immutable?

Among other things, because no LinearCongruentialEngine will ever, ever be immutable -- because it won't work if it's immutable.  Whereas ref const I believe is simply saying, "You can't mutate it via this reference."  I'm not sure if it ought technically to be auto ref const.

In any case, I think the general reason is in order to be agnostic about type qualifications.  Try:

    BigInt a = BigInt(5);
    immutable BigInt b = immutable(BigInt)(5);
    writeln(a.opEquals(b));

... which works.  BigInt.opEquals takes input by an auto ref const.
October 10, 2013
On Thursday, 10 October 2013 at 23:12:24 UTC, qznc wrote:
> The linked page clearly says "It may, however, be changed by another reference to that same data."
>
>> usually you would think that no one else should change the data while callee runs. but at least with c++ i could imagine running callee in a thread with a reference to a const thing which changes underneath and while callee is running.
>
> A const argument gives information to both sides. For the caller it means callee does not modify the data. For the callee it means somebody else (another thread) may modify the data at any time.
>
> An immutable argument means the same for the caller, but gives an additional guarantee to the callee that nobody modifies the data.

That is probably a reasonable interpretation... but I think it will only get you pain. The fact is, regardless of your interpretation of "const" arguments - the general guideline is "prefer const because immutables and mutables can be passed in". Think of it this way: you are writing a very generic function that requires inputs that you do not mutate. If you choose immutable you are making your life easier, I guess, since you know data is not being changed on you. But you are also preventing your function from being called by any arguments that are already mutable (assuming the type has mutable aliasing) because crossing the mutable to immutable divide is not part of the language. So, the only way then for mutable types to make it into your function is to copy them and this may or may not even be possible. And if you truly know no code is mutating your data it is a tougher sell.

On the other hand if you go with const parameters, your data could in fact change on you. Unfortunately, it can cause terrible angst when the only options make you wish for other options.

Have you actually written nested D data types with mutable aliasing (not just slices but assoc arrays) and used immutable in the signatures? I have tried and not succeeded.

I guess this just highlights the need for guidelines.