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

Reply via email to