Mutable enums

Timon Gehr timon.gehr at gmx.ch
Thu Nov 17 09:31:58 PST 2011


On 11/17/2011 03:19 PM, Steven Schveighoffer wrote:
> On Wed, 16 Nov 2011 17:39:16 -0500, Timon Gehr <timon.gehr at gmx.ch> wrote:
>
>> On 11/16/2011 10:56 PM, Steven Schveighoffer wrote:
>>> On Wed, 16 Nov 2011 16:16:48 -0500, Timon Gehr <timon.gehr at gmx.ch>
>>> wrote:
>>>
>>>> On 11/16/2011 09:00 PM, Steven Schveighoffer wrote:
>>>>> On Wed, 16 Nov 2011 14:26:57 -0500, Timon Gehr <timon.gehr at gmx.ch>
>>>>> wrote:
>>>>>
>>>>>> On 11/16/2011 02:22 PM, Steven Schveighoffer wrote:
>>>>>>> On Tue, 15 Nov 2011 13:45:02 -0500, Timon Gehr <timon.gehr at gmx.ch>
>>>>>>> wrote:
>>>>>>>
>>>>>>>> Note that this is an explicit allocation:
>>>>>>>>
>>>>>>>> int[] a = [1,2,3]; // just as explicit as a NewExpression
>>>>>>>>
>>>>>>>> Only the enums "hide" it sometimes, but actually you should know
>>>>>>>> the
>>>>>>>> involved types.
>>>>>>>
>>>>>>> As I've said, there are already ways to explicitly allocate
>>>>>>> memory. A
>>>>>>> suggested replacement for this is:
>>>>>>>
>>>>>>> int[] a = array(1, 2, 3);
>>>>>>>
>>>>>>> And you could always do:
>>>>>>>
>>>>>>> int[] a = [1, 2, 3].dup;
>>>>>>>
>>>>>>> Nobody complains about having to do:
>>>>>>>
>>>>>>> char[] a = "hello".dup;
>>>>>>>
>>>>>>> I don't see why we couldn't do the same for all array literals.
>>>>>>>
>>>>>>
>>>>>> Because 'immutable' behaves nicely on built-in value types, but
>>>>>> not on
>>>>>> arbitrary reference types.
>>>>>
>>>>> string is a reference type. We hear no complaints about strings being
>>>>> stored in ROM and the type of literals being immutable.
>>>>>
>>>>
>>>> string is not an immutable type. It is immutable(char)[] and char is a
>>>> built-in value type.
>>>>
>>>> static assert(isMutable!string);
>>>
>>> It fits my definition of a valid enum reference type (immutable or
>>> implicitly castable to immutable). The point is that the data referenced
>>> is stored in ROM and therefore a) immutable and b) fully defined at
>>> compile-time.
>>
>> Indeed. But fact is, the data that is qualified with immutable is not
>> of reference type therefore it behaves nicely. And you don't get that
>> in the general case.
>
> In the general case, there is always a library function for
> construction. In other words, what the compiler currently does for array
> literals can be done in a library. But a library cannot create ROM
> space. The compiler-based features (and CTFE in general) should be
> helping us create things at compile time, not at run time. We already
> have the tools to construct arrays at runtime.
>
>>
>> I am looking for something like this:
>>
>> template writeln(T...)(T){
>> alias writelnImpl!(writelnInferConst!T) writeln;
>> }
>>
>> (it is even trivial to parse, unlike normal function definitions!)
>
> What does writelnInferConst!T do? I'm afraid I'm not getting what you
> are saying.
>
> I was thinking writeln should do this:
>
> void writeln(T...)(const T args) {...}

As you pointed out, this cannot print types that have a non-const 
toString method (caching the result could be a perfectly valid reason 
for that.)

writelnInferConst finds out which parameters can be treated as const and 
still be printed so that the correct version of writeln may be called.

For example:

class Foo{ // can be printed if const
     string toString()const{return "Foo";}
}

class Bar{ // cannot be printed if const
     string cache;
     string toString(){return cache!is null?cache:(cache="Bar");}
}

template hasConstToString(T){
     enum hasConstToString = is(typeof((const T t){return t.toString();}));
}

template writelnInferConstImpl(T...){
     static if(!T.length) alias T X;
     else static if(hasConstToString!(T[0])){
             alias T[0] _;
             alias TypeTuple!(const(_),writelnInferConst!(T[1..$])) X;
     }else
         alias TypeTuple!(T[0],writelnInferConst!(T[1..$])) X;
}
template writelnInferConst(T...){alias writelnInferConstImpl!T.X 
writelnInferConst;} // (bug 6966)


static 
assert(is(writelnInferConst!(Foo,Bar,Foo,Foo,Bar)==TypeTuple!(const(Foo),Bar,const(Foo),const(Foo),Bar)));


The real thing would also do stuff like

actual argument immutable(Foo[])[] => formal argument const(Foo[][]).

In order to get rid of bloat created by pointless instantiations of 
writelnImpl.



>
>>> I have an enhancement request in for intercepting IFTI (not sure if it
>>> applies here): http://d.puremagic.com/issues/show_bug.cgi?id=4998
>>>
>>
>> It has a complexity of at least 2^numparams and probably all kinds of
>> odd implications for the compiler internals that would lead to a buggy
>> implementation.
>
> I'm not a compiler writer, but I don't see how this is the case.

There were/are quite a few error gagging related bugs. I guess this 
would be similar.

>
>>
>> I think this is a better solution:
>>
>> void foo2(T: ParameterTypeTuple!foo[0])(T t){foo(t);}
>>
>> Then it is just a matter of applying proper value range propagation
>> for IFTY:
>>
>> void bar(T: short)(T t){...}
>>
>> void main(){
>> bar(1); // ok
>> }
>
> The issue with all this is, IFTI doesn't work that way:
>
> void foo(T: short)(T t) {}
>
> void main()
> {
> foo(1);
> }
>
> testifti.d(5): Error: template testifti.foo(T : short) does not match
> any function template declaration
> testifti.d(5): Error: template testifti.foo(T : short) cannot deduce
> template function from argument types !()(int)
>
> IFTI decides the types of literals before trying to find a proper
> template to instantiate. Any possibility to intercept the decision of
> literal type or of instantiation would be useful. I think that it's
> better suited to the constraints, because there is more power there. But
> I'm not sure. If you can find a more straightforward way, I'm all for it.
>
> In any case, I need to update the bug report, because the general case
> is if foo has an overload. For instance:
>
> foo(short s);
> foo(wstring w);
>
> foo2 should be able to call both with 1 and "hello" without issue.
>
> My driving use case to create the enhancement was creating a wrapper
> type that intercepted function calls. I planned to use opDispatch, but
> it didn't quite work with literals.

Ok, I see the problem. My proposed IFTI template mechanism would save 
the day.

It would look like this (static foreach would have to be replaced by a 
recursive mixin template because Walter encountered implementation 
difficulties).


template OverloadsOf(alias symbol){ // should this be in std.traits?
     alias TypeTuple!(__traits(getOverloads, __traits(parent,symbol), 
__traits(identifier,symbol))) OverloadsOf;
}

auto wrapper(alias foo)(ParameterTypeTuple!foo args){
     return foo(args);
}
template opDispatch(string op,T...)(T){
     static foreach(foo; OverloadsOf!(mixin(op))){
         alias wrapper!foo opDispatch;
     }
     static if(OverloadsOf!(mixin(op)).length==0) { // we are dealing 
with a template function
         auto opDispatch(T args){
             return foo(args);
         }
     }
}




>
>>> I think the bloat is a wash. Every time I use an array literal, there is
>>> a complete instantiation of what would be in an array template inline in
>>> the calling function.
>>
>> With the template function you have that too because the calling
>> function builds the arguments. It is just that you also get the
>> template bloat and an additional function call. Unless it is inlined,
>> of course.
>
> You are right, I hadn't thought of that. But what about this: most array
> literals are actually literals (made up of CTFE-decided values). Making
> them ROM-stored would *eliminate* bloat as compared to the current
> implementation.

Yes, and the compiler should do that. That works fine with the current 
semantics of array literals.

>
> Also, given how template-centric D and phobos are, I think at some point
> we need to examine how to minimize template bloat in general, by
> coalescing identical code into one function, or not emitting functions
> that are always inlined, not to mention avoiding storing templates only
> used at compile-time in the code (e.g. isInputRange).
>

I agree.

>>> Yes, it breaks code. It's worth it. To avoid a heap allocation for [1,
>>> 2, 3] is worth breaking code that uses runtime data in an array literal.
>>> The compiler can be helpful in the error message:
>>>
>>> Error somefile.d(345): array literals cannot contain runtime data. Maybe
>>> you meant:
>>> array(new Foo, new Bar, new Qux);
>>>
>>> I want to point out that currently there is *NO* way to make an
>>> immutable array literal that lives in ROM. This is unacceptable.
>>>
>>
>> There is:
>>
>> void main(){
>> static immutable x = [1,2,3];
>> }
>
> Seems rather odd you should have to jump through these hoops to get the
> compiler to use ROM space. But I concede that I did not know of this
> trick. It does not sway my opinion that CTFE should produce ROM-stored
> references, and library function should be used for runtime-constructed
> references.
>

CTFE should produce ROM-stored data iff it is used during run time, I 
agree on that. However if the enum is typed as mutable, it should create 
a copy of the ROM-stored data.











More information about the Digitalmars-d-learn mailing list