August 19, 2016
On Friday, 19 August 2016 at 23:12:39 UTC, Engine Machine wrote:
> On Friday, 19 August 2016 at 22:20:09 UTC, Enamex wrote:
>> On Friday, 19 August 2016 at 18:25:06 UTC, Engine Machine wrote:
>>> [...]
>>
>> Something like this:
>>
>> class Type(T: typeof(null)) { //< L1 (specialization)
>>     int x;
>> }
>>
>> class Dog {}
>>
>> class Type(T) : Type!(typeof(null)) { //< L2 (`typeof(null)`)
>>     static if(is(T: Dog)) //< L3 (`is(MyType: IntendedType)` or `is(MyType == ExactType)`)
>>         int y;
>> }
>>
>> What you're looking for is "specialization", on line "L1". Also some corrections on lines "L2" and "L3"
>
> How is this any different, besides adding meaningless complexity, to inheritence as it already is?

Um, not sure what you mean. This isn't suggesting adding anything; it's already legal/compiles.
August 19, 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 ;)




















August 20, 2016
On Fri, 19 Aug 2016 23:43:05 +0000, Engine Machine wrote:
> 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.

No. You must weigh the added complexity against the benefits. The less frequently a feature will be used, the more surprise you will inflict on people when they encounter it. The less like other things in the language this new feature is, the more awkward it will be for users.

You called your suggestion "syntactic sugar", but if we can call it that, we can say the same of templates. Your implication that it is a simple change is incorrect.

Your proposal clashes with existing features. It's even a breaking change. It's implemented easily enough by hand with:

class SomeFoo {
  // common fields and methods, possibly abstract
}

class Foo(T) : SomeFoo {
  // everything else
}

The by-hand implementation is also more obvious.

You would have to change the proposed syntax, show that this can't be reasonably done in a library, and show what it gives you that's appreciably better than what's currently there.
August 20, 2016
On 20.08.2016 00:07, Engine Machine wrote:
> On Friday, 19 August 2016 at 21:07:42 UTC, Timon Gehr wrote:
>> On 19.08.2016 20:25, Engine Machine wrote:
>>> So we can create types relationships easier:
>>>
>>> class Type(T) : Type!null
>>> {
>>>    int x;
>>>    static if (T is Dog)
>>>        int y;
>>
>>
>> alias Seq(T...)=T;
>>
>> template TypeParent(T...) if(T.length==1){
>>     static if(is(typeof(T[0])==typeof(null))) alias TypeParent = Seq!();
>>     else alias TypeParent = Seq!(Type!null);
>> }
>>
>> class Type(T...): TypeParent!T if(T.length==1){
>>    int x;
>>    static if (T is Dog)
>>        int y;
>> }
>
> This is a bit verbose

Apart from the workaround for the ridiculous alias template parameter semantics, I think the length of the code more or less matches the specificity of the requested behaviour. (There should be ways to abstract out most of it, in case you need this really often.)

> and not quite right (T is Dog should be something
> like T[0], or whatever).
> ...

('T is Dog' does not work anyway.)

> It does essentially work. My only complaint is that it would be nice to
> be able to export an alias to Type!() = Type; in the namespace of the
> type being created. Doubt that D can do that!? If it can, then it should
> be an adequate solution.
>
> That is
>
> It would be nice to have something like
>
> alias Type = Type!();
> class Type(T...): TypeParent!T if(T.length==1){
>     int x;
>     static if (T is Dog)
>         int y;
> }

I don't understand how this is related.
August 20, 2016
On Friday, 19 August 2016 at 18:25:06 UTC, Engine Machine wrote:
> So we can create types relationships easier:
>
> class Type(T) : Type!null
> {
>    int x;
>    static if (T is Dog)
>        int y;
> }
>
> Type == Type!null (!null implicit, only if defined in the above way. Essentially an alias is created for us automatically)

This syntax would be very confusing to a non-expert. It is a special case of an existing features (inheritance and templates), which makes it hard to learn about, as all resources are going to discuss those other features first, and only the most detailed readings are going to contain this detail. Moreover, it does not introduce a keyword or any other name, so it is almost impossible to Google. Try googling something like "template<class T> class a class b: public a<b>", and see which result contains "curiously recurring template pattern". It is hard to find, isn't it?

Moreover, as indicated by another poster, null is already a valid template parameter, making it even more confusing. Also it is unclear when the interpretation would be as you propose.

You need to appreciate the difference between write-only code and code that can be easily read, reviewed and understood.

Dlang has already went too far in inventing pieces of non-obvious syntax for template features. Lets not make the situation worse.

Distill what you want to do, see what use cases are covered by other features and libraries, name your thing accordingly and then propose.
August 20, 2016
On Saturday, 20 August 2016 at 13:45:59 UTC, poliklosio wrote:
> On Friday, 19 August 2016 at 18:25:06 UTC, Engine Machine wrote:
>> So we can create types relationships easier:
>>
>> class Type(T) : Type!null
>> {
>>    int x;
>>    static if (T is Dog)
>>        int y;
>> }
>>
>> Type == Type!null (!null implicit, only if defined in the above way. Essentially an alias is created for us automatically)
>
> This syntax would be very confusing to a non-expert. It is a special case of an existing features (inheritance and templates), which makes it hard to learn about, as all resources are going to discuss those other features first, and only the most detailed readings are going to contain this detail. Moreover, it does not introduce a keyword or any other name, so it is almost impossible to Google. Try googling something like "template<class T> class a class b: public a<b>", and see which result contains "curiously recurring template pattern". It is hard to find, isn't it?
>
> Moreover, as indicated by another poster, null is already a valid template parameter, making it even more confusing. Also it is unclear when the interpretation would be as you propose.
>
> You need to appreciate the difference between write-only code and code that can be easily read, reviewed and understood.
>
> Dlang has already went too far in inventing pieces of non-obvious syntax for template features. Lets not make the situation worse.
>
> Distill what you want to do, see what use cases are covered by other features and libraries, name your thing accordingly and then propose.

Do you not realize that if every human endeavor was to bow down to the lowest common denominator there would be no point?

If Walter had the notion "I have to make D so the beginner can understand it easily" Then D would be javascript or php?

If you want to be an idiot and use an idiot language, then there are many out there. Go program in BASIC.

So your arguments appealing to ignorance(not the logical fallacy, of course) is non-sense.  Life is complex, stop trying to reduce it to something you already can understand. I think, technically, this is called laziness.






August 20, 2016
On Saturday, 20 August 2016 at 10:04:07 UTC, Timon Gehr wrote:
> On 20.08.2016 00:07, Engine Machine wrote:
>> On Friday, 19 August 2016 at 21:07:42 UTC, Timon Gehr wrote:
>>> On 19.08.2016 20:25, Engine Machine wrote:
>>>> So we can create types relationships easier:
>>>>
>>>> class Type(T) : Type!null
>>>> {
>>>>    int x;
>>>>    static if (T is Dog)
>>>>        int y;
>>>
>>>
>>> alias Seq(T...)=T;
>>>
>>> template TypeParent(T...) if(T.length==1){
>>>     static if(is(typeof(T[0])==typeof(null))) alias TypeParent = Seq!();
>>>     else alias TypeParent = Seq!(Type!null);
>>> }
>>>
>>> class Type(T...): TypeParent!T if(T.length==1){
>>>    int x;
>>>    static if (T is Dog)
>>>        int y;
>>> }
>>
>> This is a bit verbose
>
> Apart from the workaround for the ridiculous alias template parameter semantics, I think the length of the code more or less matches the specificity of the requested behaviour. (There should be ways to abstract out most of it, in case you need this really often.)

Yes, it is not too bad. One doesn't need to T.length part so it is a little shorter. Took me a bit to get used to it.


>
>> and not quite right (T is Dog should be something
>> like T[0], or whatever).
>> ...
>
> ('T is Dog' does not work anyway.)
>

Yes, I use strings, but it doesn't really matter, it can be made to work.



>> It does essentially work. My only complaint is that it would be nice to
>> be able to export an alias to Type!() = Type; in the namespace of the
>> type being created. Doubt that D can do that!? If it can, then it should
>> be an adequate solution.
>>
>> That is
>>
>> It would be nice to have something like
>>
>> alias Type = Type!();
>> class Type(T...): TypeParent!T if(T.length==1){
>>     int x;
>>     static if (T is Dog)
>>         int y;
>> }
>
> I don't understand how this is related.


The only difference is the alias Type = Type!(); Again, D can't do this but the point is that it would be nice to have the alias. One can't do everything as a "library" solution.

Trying to expand your code results in some odd behavior:


public template TypeParent(P)
{	
	import std.traits;
	alias T = TemplateArgsOf!P;
	alias Seq(T...) = T;
    static if (T.length == 0 || is(typeof(T[0]) == typeof(null)))
	{
		alias TypeParent = Seq!();		
	}
    else
	{
		alias TypeParent = Seq!(P!(T[0..T.length-1]));
	}
}


class Type(T...) : TypeParent!(Type!T)
{
	int x;
	static if (T.length >= 1 && T[0] is "Animal")
	{
		int y;
		static if (T.length >= 2 && T[1] is "Dog")
		{
			int z;
			static if (T.length >= 3&& T[2] is "Pug")
			{
				int s;
			}
		}

	}
}


void main()
{

	import std.traits;

	auto a = new Type!("Animal", "Dog", "Pug")();
	Type!("Animal", "Dog") b = a;	
	Type!("Animal") c = b;	

	a.s = 1;
	b.z = 2;
	c.y = 3;
}

b and c are of type P!, not Type!
August 21, 2016
On 20.08.2016 21:20, Engine Machine wrote:
>>>
>>> That is
>>>
>>> It would be nice to have something like
>>>
>>> alias Type = Type!();
>>> class Type(T...): TypeParent!T if(T.length==1){
>>>     int x;
>>>     static if (T is Dog)
>>>         int y;
>>> }
>>
>> I don't understand how this is related.
>
>
> The only difference is the alias Type = Type!(); Again, D can't do this
> but the point is that it would be nice to have the alias. One can't do
> everything as a "library" solution.
> ...

I see what you are after (but this was not part of the original requirements :)   ). I don't think there's a way to make a symbol act as both a type and a template.


> Trying to expand your code results in some odd behavior:
>
>
> public template TypeParent(P)
> {
>     import std.traits;
>     alias T = TemplateArgsOf!P;
>     alias Seq(T...) = T;
>     static if (T.length == 0 || is(typeof(T[0]) == typeof(null)))
>     {
>         alias TypeParent = Seq!();
>     }
>     else
>     {
>         alias TypeParent = Seq!(P!(T[0..T.length-1]));
>     }
> }
>
>
> class Type(T...) : TypeParent!(Type!T)
> {
>     int x;
>     static if (T.length >= 1 && T[0] is "Animal")
>     {
>         int y;
>         static if (T.length >= 2 && T[1] is "Dog")
>         {
>             int z;
>             static if (T.length >= 3&& T[2] is "Pug")
>             {
>                 int s;
>             }
>         }
>
>     }
> }
>
>
> void main()
> {
>
>     import std.traits;
>
>     auto a = new Type!("Animal", "Dog", "Pug")();
>     Type!("Animal", "Dog") b = a;
>     Type!("Animal") c = b;
>
>     a.s = 1;
>     b.z = 2;
>     c.y = 3;
> }
>
> b and c are of type P!, not Type!

It seems that this is a compiler bug. Is the problem just with getting a string representation of the type?
August 21, 2016
On Sunday, 21 August 2016 at 00:44:01 UTC, Timon Gehr wrote:
> On 20.08.2016 21:20, Engine Machine wrote:
>>>>
>>>> That is
>>>>
>>>> It would be nice to have something like
>>>>
>>>> alias Type = Type!();
>>>> class Type(T...): TypeParent!T if(T.length==1){
>>>>     int x;
>>>>     static if (T is Dog)
>>>>         int y;
>>>> }
>>>
>>> I don't understand how this is related.
>>
>>
>> The only difference is the alias Type = Type!(); Again, D can't do this
>> but the point is that it would be nice to have the alias. One can't do
>> everything as a "library" solution.
>> ...
>
> I see what you are after (but this was not part of the original requirements :)   ). I don't think there's a way to make a symbol act as both a type and a template.

Well, I see that a template with 0 parameters can act as a "type", if you will.

Just like functions

void foo(T)(T x)

acts like a normal function foo(3) even though it is a templated function.

In fact, I see very little difference between a template with 0 parameters and a type.

Type!() = Type

seems very natural an logical to me as long as Type isn't defined anywhere else... but that is a problem in all cases(e.g., even functions).

Maybe there is some good reason though that simplifies the compiler.

>
>> Trying to expand your code results in some odd behavior:
>>
>>
>> public template TypeParent(P)
>> {
>>     import std.traits;
>>     alias T = TemplateArgsOf!P;
>>     alias Seq(T...) = T;
>>     static if (T.length == 0 || is(typeof(T[0]) == typeof(null)))
>>     {
>>         alias TypeParent = Seq!();
>>     }
>>     else
>>     {
>>         alias TypeParent = Seq!(P!(T[0..T.length-1]));
>>     }
>> }
>>
>>
>> class Type(T...) : TypeParent!(Type!T)
>> {
>>     int x;
>>     static if (T.length >= 1 && T[0] is "Animal")
>>     {
>>         int y;
>>         static if (T.length >= 2 && T[1] is "Dog")
>>         {
>>             int z;
>>             static if (T.length >= 3&& T[2] is "Pug")
>>             {
>>                 int s;
>>             }
>>         }
>>
>>     }
>> }
>>
>>
>> void main()
>> {
>>
>>     import std.traits;
>>
>>     auto a = new Type!("Animal", "Dog", "Pug")();
>>     Type!("Animal", "Dog") b = a;
>>     Type!("Animal") c = b;
>>
>>     a.s = 1;
>>     b.z = 2;
>>     c.y = 3;
>> }
>>
>> b and c are of type P!, not Type!
>
> It seems that this is a compiler bug. Is the problem just with getting a string representation of the type?

I think it is just that D isn't "unaliasing" the template parameter, P in this case, to what it aliases... It makes it seem like things are different. Why one would have to keep track of internal template aliases is beyond me. D should rewrite the internal parameters in to meaningful external scope parameters if possible(which should be possible since it must know the input to the template).


August 22, 2016
On Sunday, 21 August 2016 at 21:21:22 UTC, Engine Machine wrote:
> Well, I see that a template with 0 parameters can act as a "type", if you will.
>
> Just like functions
>
> void foo(T)(T x)
>
> acts like a normal function foo(3) even though it is a templated function.
>
> In fact, I see very little difference between a template with 0 parameters and a type.
>
> Type!() = Type

I think this is a very bad idea theory-wise, if not in practice as well. A template is a type constructor, not a type. They are two very different things. You can get the size of a type, its members, etc. while you cannot for a template.