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