Reading about D: few questions

Ali Çehreli acehreli at yahoo.com
Fri Dec 23 16:46:48 PST 2011


On 12/23/2011 03:16 PM, Jonathan M Davis wrote:
 > On Friday, December 23, 2011 09:47:35 Ali Çehreli wrote:
 >> - To be more useful, function parameters should not insist on immutable
 >> data, yet we type string all over the place.
 >
 > That depends. If they're going to have to idup the data anyway, then it's
 > better to require that the argument be immutable so that that cost is 
clear.
 >
 > The worst is taking const and then iduping, because then you're 
forced to idup
 > strings which didn't need to be iduped.

That would be leaking an implementation detail to the user. Besides, it 
doesn't solve the problem if the user is in the middle:

void user(const(char)[] p)
{
     writeln(endWithDot(p));
}

The user itself would be forced to take immutable, but this time the 
reason is different: not because he is passing a copy optimization of 
its own, but because he is passing endWithDot()'s copy optimization to 
its caller.

immutable would have to be leaked all the way up just because a low 
level function decided to make a copy!

Perhaps the guideline should be: Everybody should take by immutable 
references so that this leaking of immutable through all layers should 
not be a problem in case a low-level function decided to make a copy.

 > And in general, operating on strings is more efficient than mutable 
character
 > arrays, because you can slice them with impunity, whereas you often 
have to
 > dup or idup mutable arrays in order to avoid altering the original data.

Agreed. But immutable on the parameter list is an insistence: The 
function insists that the data be immutable. Why? Because it is going to 
store it for later use? Perhaps share it between threads? It is 
understandable when there is such a legitimate reason. Then the caller 
would see the reason too: "oh, takes immutable; that means my data may 
be used later as is."

 > That being said, an increasing number of functions in Phobos are 
templated on
 > string type so that you can use whatever string type that you want 
with them.
 > And there is a push (at least with toString) to add the ability to 
put the
 > result of a string function into an existing string of some variety 
(be it
 > using a delegate or an output range). So, you'll be forced to use 
string less,

Good. Ranges are more and more becoming "thinking in D." Perhaps we 
should be talking about a range that appends a dot at the end of the 
existing elements.

 > but the reality of the matter is that in the general case you should 
probably
 > be using string anyway (there are, of course, always exceptions).

I am looking for simple guidelines when designing functions. It is 
simple in C++: take data by reference to const if you are not going to 
modify it. (It is questionable whether small structs should be passed by 
value instead, but that's beside the point.)

In C++, passing by reference to const works because the function accepts 
any type of mutability and a copy is avoided because it's a reference.

In D, immutable is not more const than const (which was my initial 
assumption); it is an additional requirement: give me data that should 
never change. My point is that this requirement makes sense only in rare 
cases. Why would a function like endWithDot() insist on how mutable the 
user's data is?

 >> - To be more useful, functions should not insist on the mutability of
 >> the data that they return.
 >>
 >> The following function makes a new string:
 >>
 >> char[] endWithDot(const(char)[] s)
 >> {
 >>       return s ~ '.';
 >> }
 >>
 >>       char[] s;
 >>       s ~= "hello";
 >>       auto a = endWithDot(s);
 >>
 >> It is good that the parameter is const(char) so that I could pass the
 >> mutable s to it.
 >>
 >> But the orthogonal problem of the type of the return is troubling. The
 >> result is clearly mutable yet it can't be returned as such:
 >>
 >> Error: cannot implicitly convert expression (s ~ '.') of type
 >> const(char)[] to char[]
 >>
 >> We've talked about this before. There is nothing in the language that
 >> makes me say "the returned object is unique; you can cast it to mutable
 >> or immutable freely."
 >
 > In general, D doesn't have features where the programmer says that 
something
 > is okay. It's too interested in making guarantees for that. Either it can
 > guarantee something, or you force it with a cast. I can't think of 
even one
 > feature where you say that _you_ guarantee that something is okay. 
Casting is
 > your only option. [...]

I know. I used the wrong words. Yes, the compiler should see what I see: 
the returned object is unique and can be elevated to any mutability level.

 > Your particular example is quite easily fixed though. The issue is 
that the
 > string which was passed in is typed as const(char)[], and the 
expression s ~
 > '.' naturally results in the same type. But it's quite clear that the
 > resulting string could be of any constness, since it's a new string. 
So, just
 > tell it what constness to have by casting it.

That's the other side of the problem: Why would the function dictate how 
the caller should treat this piece of data? The function should not 
arbitrarily put const or immutable on the data. That would be making it 
less useful. The data is mutable anyway.

inout doesn't solve this problem as it is a connection between the 
mutability of the parameter(s) and the result. The mutability type of 
the result has nothing to do with the parameters' in the case of 
functions like endWithDot().

As you say, maybe the situation will get better in D and functions will 
simply return char[] and the compiler will convert it to automatically.

I remembered that I had shown UniqueMutable when we discussed this issue 
last time:

import std.stdio;
import std.exception;

struct UniqueMutable(T)
{
     T data;
     bool is_used;

     this(ref T data)
     {
         this.is_used = false;
         this.data = data;
         data = null;
     }

     T as_mutable()
     {
         return as_impl!(T)();
     }

     immutable(T) as_immutable()
     {
         return as_impl!(immutable(T))();
     }

     private ConvT as_impl(ConvT)()
     {
         enforce(!is_used);
         ConvT result = cast(ConvT)(data);
         data = null;
         is_used = true;
         return result;
     }
}

UniqueMutable!T unique_mutable(T)(ref T data)
{
     return UniqueMutable!T(data);
}

UniqueMutable!(char[]) foo()
{
     char[] result = "hello".dup;
     result ~= " world";
     return unique_mutable(result);
}

void main()
{
     char[] mutable_result = foo().as_mutable;
     mutable_result[0] = 'H';
     string immutable_result = foo().as_immutable;
}

 >
 > - Jonathan M Davis

Ali



More information about the Digitalmars-d-learn mailing list