How about a special null template parameter?

Engine Machine via Digitalmars-d digitalmars-d at puremagic.com
Fri Aug 19 16:43:05 PDT 2016


On Friday, 19 August 2016 at 22:20:29 UTC, Lodovico Giaretta 
wrote:
> On Friday, 19 August 2016 at 21:51:38 UTC, Engine Machine wrote:
>> On Friday, 19 August 2016 at 19:19:35 UTC, Lodovico Giaretta 
>> wrote:
>>> 1) `null` has a very precise meaning: it is the only valid 
>>> value of an unnamed type only known as `typeof(null)`, which 
>>> implicitly converts to any class/pointer/dynamic 
>>> array/associative array type. Thus this thing should have a 
>>> different name.
>>
>> null as many interpretations. It is ok to expand the meaning. 
>> It is done all the time. If it confuses you are creates a 
>> semantic difficulty, I don't mind what it is called. It could 
>> be called Boomfurkasufasdf.
>>
>> In this case, defining it to be null fits in semantically 
>> though. If it creates some compiler issue then, again, 
>> anything else could be used. I'd prefer Null or NULL or Aleph 
>> or something short and meaningless.
>
> The problem is that null is a valid value to instantiate an 
> alias template parameter with. So using it to do something 
> completely different may create ambiguity (does that null means 
> "parent template" or is it a valid instantiation? Who knows). 
> But as you said, this is just a matter of finding a name.
>

Yes, but in this case it is used in a very specific and exact 
sense, so it is not arbitrarily ambiguous. Again, if it is, then 
just use some other syntax.

>
>>> 2) What about this case:
>>> ```
>>> class Type(T): Type
>>> {
>>>     static if (T is Dog)
>>>         int y;
>>>     else
>>>         float z;
>>> }
>>> ```
>>> What should be inside the base?
>>
>> z.
>
> This is wrong. If Type has to be the base class of every 
> Type!T, then it cannot contain z, as there is one Type!T 
> (namely Type!Dog) which does not contain z. So in this case the 
> base class Type should be empty. As you see it is not that 
> simple.

Yeah, T should be empty. This is not a good example though 
because one wouldn't compose such things like this. It is quite 
simple when one follows the logical guidelines. The code would 
"work" but the inheritance hierarchy would not be correct and the 
compiler would spit out errors, as it should.

So, even if I mis-reasoned about it(quickly glanced over it 
without thinking), it doesn't change anything though.

Base would be empty and technically we would have two types of 
derived types. One of Dog with y in it and everything else with z 
in it, call it E.

Dog and E would just be derived types of Type.

It is no different than

class Type(T): Type
{
    static if (T is Dog)
         int y;
    else static if (T !is Dog)
         float z;
}


which is semantically equivalent to

class Type(T): Type
{
    static if (T is Dog)
         int y;
    else static if (T is E)
         float z;
}

Where E is everything that is not a Dog.



>>> 4) I think that the value of this addition is little wrt. the 
>>> amount of work the compiler should do to implement it (which 
>>> at first sight would be a lot). But this is of course just my 
>>> opinion.
>>
>> It is just syntactic sugar and requires no complex compiler 
>> modifications. The benefit is reduced visual complexity and 
>> that is always a bonus. That is the point we don't write in 
>> 0's and 1's... everything else is just syntactic sugar. In 
>> this case, it is a very simple rewrite rule. A few lines of 
>> code would be all that is required.
>
> I'm not an expert, but I think this requires *a lot* of 
> compiler work: it has to analyze every symbol in the class to 
> find out whether it must be part of the "uninstantiated" base 
> or not. And this kind of analysis may not be easy (it may be 
> masked by the use of traits, template mixins, string mixins and 
> so on). Even humans can easily get it wrong (see my previous 
> example).

Yes, it might. But if it is truly just semantic sugar, then it 
shouldn't pose any problem because it's just a rewrite. Obviously 
I am not suggesting it just be written up in a New York minute 
and thrown in the code. It needs to be properly analyzed and such.



> Also, I thought of other problems:
> - Interaction with non-class templates: how should they behave? 
> Or are we just introducing a special case for class templates? 
> (Special cases are very bad, we should limit them).
> - Interaction with base classes and interfaces: how is this 
> "uninstantiated base class" inserted into the graph of classes 
> and interfaces? Are interfaces considered implemented by this 
> base class? Is this base class inserted between the templated 
> class and the "original" base class?

I think you are making it too complicated. This is a grammar 
transformation. If the syntax is grammatically equivalent to 
something already implementable in the language, then one only 
has to make sure the new syntactic sugar does not collide with 
pre-existing semantic evaluations.

Basically, if the compiler can always transform it in to valid 
code and the new syntax doesn't create ambiguous or wrong 
interpretations, then it is valid.

Since I've already said that basically it is a simplification of 
the type system for classes, and have hinted at the rewrite 
rules(of course, I didn't specify them with mathematical 
exactness because there is no point, it isn't my job and I am not 
qualified in the first place), really only the second issue has 
to be addressed. But the second issue generally just finding the 
proper syntax(e.g., use null or not).

People that are familiar with D's internals are far better 
equipped to know these issues than I am. It isn't that it can't 
be done, it is, "How much do the D maintainers care enough about 
the problem". It is generally that they don't care too much about 
it because they themselves don't see it as a problem. Hence, why 
should I make such efforts to specify things exactly when chances 
are it won't get implemented in the first place?

Generally it's "If it can be done in the library then it should". 
And since this can be done, more or less, in a library and it is 
not a significant language enhancement, it won't be implement in 
D.

For example,

If I propose the following syntactic sugar:

class Alpha^5 { }

which expands to

class Alpha1 { }
class Alpha2 { }
class Alpha3 { }
class Alpha4 { }
class Alpha5 { }

then the only question is can the current compiler interpret ^n 
unambiguously and without undue complexity. If it can, and such a 
syntactic rewrite is useful, then it should be implemented.

It is obvious that the transformation itself does not introduce 
anything new and hence, in and of itself, does not break anything.

In fact, by definition, it can't. If something has well defined 
mapping rules between the new semantics and the old, then it 
can't break anything inherently. Only the syntax itself could 
create problems for the specific implementation(e.g., using null 
which then creates ambiguity or problems in parsing).  But syntax 
is relatively meaningless. Any syntax will do, I am not too 
concerned about such trivialities as long as it doesn't increase 
the language complexity(which is why I suggested such a shorthand 
in the first place, because it is simpler).

My point is, with all this mumbo jumbo, is that we shouldn't 
"argue" over whether short hand notation is correct or not, if 
the mapping is pretty clear(should be crystal clear if it is 
implemented but that's for later). Arguing over how much it adds 
to the language vs taking away and the proper syntax is what is 
important.

To be precise: If a language L1 and pre-processor P can transform 
in to a new language L2 = P(L1), and L2 is valid in some 
respect(such as a properly comparable program) then L1 is valid 
as an extension of L2. This is function composition and just 
works thanks to mathematics.

The only question, is it worth while to "update"(implement the 
pre-processor as part of the compiler, say) L2 to become L1, so 
we don't need a pre-processor. I can't answer this because what 
is worth while to me is not necessarily worth-while to those that 
can actually do something about it. If I cared enough, I would 
fork dmd or implement my own language and compiler...


Anyways, all of it is meaningless, not like this has a shot at 
actually being implemented. Luckily there seems to be a good 
enough approximate solution that I can sleep at night ;)






















More information about the Digitalmars-d mailing list