www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - What type functions mean on a language level

reply Stefan Koch <uplink.coder googlemail.com> writes:
Good Evening and Happy new year to everyone.

After a stressful move to the UK in uncertain times, I am now 
free and able to enjoy the last day of my holiday.

I am currently not working on DMD but on a different hobby 
project instead.

So with some distance between me and the implementation details I 
think it's a good idea to talk about what type functions mean for 
the language (regardless of all the architecture issues  in DMD 
which make an implementation iffy)

Specification wise type functions require two statements to be 
added.

"Under certain conditions a type may be implicitly converted to a 
value of type __type__."
and
"Under certain conditions a value of type __type__ may be 
converted to a type."

That's it.

Everything else is just working regularly with value and 
expressions.

let me give you an example.

enum TK
{
   Integral,
   Floating,
   Other,
}

string ts(__type__ t)
{
   switch (t)
   {
      case ubyte, byte, ushort, short, int, uint, long, ulong:
          return TK.Integral;
      case double, float, real :
          return TK.Floating;
      default:
          return TK.Other;
   }
}


this looks like it would require a new construct right?
infact it does not.

since the types are used in a ctfe context where values are 
expected they get implicitly converted to values.

so what happens is:
string ts(__type__ t)
{
      case cast(__type__)ubyte, cast(__type__)byte, 
cast(__type__)ushort, cast(__type__)short, cast(__type__)int, 
cast(__type__)uint, cast(__type__)long, cast(__type__)ulong:
          return TK.Integral;
      case cast(__type__)double, cast(__type__)float, 
cast(__type__)real :
          return TK.Floating;
      default:
          return TK.Other;
   }
}

which converts the types into values, the same stuff as integers 
:)
and since a switch works with integers it works with types as 
well.

And that is precisely the reason why I was so excited about my 
invention/discovery.

It just fits in. And requires minimal changes to the spec.

You will see that I've punted on the definition of the 
circumstances in which the implicit conversion from type to value 
and vice versa may occur.
That's just to keep the post short and avoid many long and boring 
words ;)

I do hope you get as excited as me when reading this.

Regards,
Stefan
Jan 01 2021
next sibling parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Friday, 1 January 2021 at 23:26:58 UTC, Stefan Koch wrote:
 Good Evening and Happy new year to everyone.

 After a stressful move to the UK in uncertain times, I am now 
 free and able to enjoy the last day of my holiday.

 I am currently not working on DMD but on a different hobby 
 project instead.

 So with some distance between me and the implementation details 
 I think it's a good idea to talk about what type functions mean 
 for the language (regardless of all the architecture issues  in 
 DMD which make an implementation iffy)

 Specification wise type functions require two statements to be 
 added.

 "Under certain conditions a type may be implicitly converted to 
 a value of type __type__."
 and
 "Under certain conditions a value of type __type__ may be 
 converted to a type."

 That's it.

 Everything else is just working regularly with value and 
 expressions.

 let me give you an example.

 enum TK
 {
   Integral,
   Floating,
   Other,
 }

 string ts(__type__ t)
 {
   switch (t)
   {
      case ubyte, byte, ushort, short, int, uint, long, ulong:
          return TK.Integral;
      case double, float, real :
          return TK.Floating;
      default:
          return TK.Other;
   }
 }


 this looks like it would require a new construct right?
 infact it does not.

 since the types are used in a ctfe context where values are 
 expected they get implicitly converted to values.

 so what happens is:
 string ts(__type__ t)
 {
      case cast(__type__)ubyte, cast(__type__)byte, 
 cast(__type__)ushort, cast(__type__)short, cast(__type__)int, 
 cast(__type__)uint, cast(__type__)long, cast(__type__)ulong:
          return TK.Integral;
      case cast(__type__)double, cast(__type__)float, 
 cast(__type__)real :
          return TK.Floating;
      default:
          return TK.Other;
   }
 }

 which converts the types into values, the same stuff as 
 integers :)
 and since a switch works with integers it works with types as 
 well.

 And that is precisely the reason why I was so excited about my 
 invention/discovery.

 It just fits in. And requires minimal changes to the spec.

 You will see that I've punted on the definition of the 
 circumstances in which the implicit conversion from type to 
 value and vice versa may occur.
 That's just to keep the post short and avoid many long and 
 boring words ;)

 I do hope you get as excited as me when reading this.

 Regards,
 Stefan
Why can't __type__ be named "any" as indication that it is a top type for all types?
Jan 01 2021
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Friday, 1 January 2021 at 23:32:25 UTC, 12345swordy wrote:
 
 Why can't __type__ be named "any" as indication that it is a 
 top type for all types?
__type__ is the type of types, it is not Top. well technically it's the type of type values which are not types themselves but may be converted to types, and be created from types. Also the name any is already used by phobos templates, making it unavailable. After type functions are in; I plan on generalizing further and introduce a proper TOP type. Which can bind to anything (like T... in a template) But for now I want to gently introduce the concept.
Jan 01 2021
parent Jacob Carlborg <doob me.com> writes:
On 2021-01-02 00:55, Stefan Koch wrote:

 After type functions are in; I plan on generalizing further and 
 introduce a proper TOP type.
 Which can bind to anything (like T... in a template)
Isn't that what `alias` parameters do? -- /Jacob Carlborg
Jan 02 2021
prev sibling next sibling parent reply Max Haughton <maxhaton gmail.com> writes:
On Friday, 1 January 2021 at 23:26:58 UTC, Stefan Koch wrote:
 Good Evening and Happy new year to everyone.

 After a stressful move to the UK in uncertain times, I am now 
 free and able to enjoy the last day of my holiday.

 I am currently not working on DMD but on a different hobby 
 project instead.

 So with some distance between me and the implementation details 
 I think it's a good idea to talk about what type functions mean 
 for the language (regardless of all the architecture issues  in 
 DMD which make an implementation iffy)

 Specification wise type functions require two statements to be 
 added.

 "Under certain conditions a type may be implicitly converted to 
 a value of type __type__."
 and
 "Under certain conditions a value of type __type__ may be 
 converted to a type."

 That's it.

 Everything else is just working regularly with value and 
 expressions.

 let me give you an example.

 enum TK
 {
   Integral,
   Floating,
   Other,
 }

 string ts(__type__ t)
 {
   switch (t)
   {
      case ubyte, byte, ushort, short, int, uint, long, ulong:
          return TK.Integral;
      case double, float, real :
          return TK.Floating;
      default:
          return TK.Other;
   }
 }


 this looks like it would require a new construct right?
 infact it does not.

 since the types are used in a ctfe context where values are 
 expected they get implicitly converted to values.

 so what happens is:
 string ts(__type__ t)
 {
      case cast(__type__)ubyte, cast(__type__)byte, 
 cast(__type__)ushort, cast(__type__)short, cast(__type__)int, 
 cast(__type__)uint, cast(__type__)long, cast(__type__)ulong:
          return TK.Integral;
      case cast(__type__)double, cast(__type__)float, 
 cast(__type__)real :
          return TK.Floating;
      default:
          return TK.Other;
   }
 }

 which converts the types into values, the same stuff as 
 integers :)
 and since a switch works with integers it works with types as 
 well.

 And that is precisely the reason why I was so excited about my 
 invention/discovery.

 It just fits in. And requires minimal changes to the spec.

 You will see that I've punted on the definition of the 
 circumstances in which the implicit conversion from type to 
 value and vice versa may occur.
 That's just to keep the post short and avoid many long and 
 boring words ;)

 I do hope you get as excited as me when reading this.

 Regards,
 Stefan
I like this idea quite a lot, some questions/points: 1. How far does this go? Are we just exposing the frontends concept of a type (i.e. the contents of dmd.mtype) or a abstraction over it? Can I (say) make an entirely new type like a struct like I would with a mixin? 2. I don't think avoiding spec changes is necessarily a good thing - D is already fairly murky in that regard so (wrt previous point) specifying exactly what typefunctions can do is probably a good thing. 3. I think a similar thing wouldn't be all that bad for the AST - read-only I should say (the only that worries me in the abstract about that is that dmd's AST isn't very consistent in its API) - i.e. Being able to access it as a regular (say) class at CTFE rather than adding more and more and more __traits.
Jan 01 2021
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Friday, 1 January 2021 at 23:52:58 UTC, Max Haughton wrote:
 On Friday, 1 January 2021 at 23:26:58 UTC, Stefan Koch wrote:
 Good Evening and Happy new year to everyone.

 After a stressful move to the UK in uncertain times, I am now 
 free and able to enjoy the last day of my holiday.

 I am currently not working on DMD but on a different hobby 
 project instead.

 So with some distance between me and the implementation 
 details I think it's a good idea to talk about what type 
 functions mean for the language (regardless of all the 
 architecture issues  in DMD which make an implementation iffy)

 Specification wise type functions require two statements to be 
 added.

 "Under certain conditions a type may be implicitly converted 
 to a value of type __type__."
 and
 "Under certain conditions a value of type __type__ may be 
 converted to a type."

 That's it.

 Everything else is just working regularly with value and 
 expressions.

 let me give you an example.

 enum TK
 {
   Integral,
   Floating,
   Other,
 }

 string ts(__type__ t)
 {
   switch (t)
   {
      case ubyte, byte, ushort, short, int, uint, long, ulong:
          return TK.Integral;
      case double, float, real :
          return TK.Floating;
      default:
          return TK.Other;
   }
 }


 this looks like it would require a new construct right?
 infact it does not.

 since the types are used in a ctfe context where values are 
 expected they get implicitly converted to values.

 so what happens is:
 string ts(__type__ t)
 {
      case cast(__type__)ubyte, cast(__type__)byte, 
 cast(__type__)ushort, cast(__type__)short, cast(__type__)int, 
 cast(__type__)uint, cast(__type__)long, cast(__type__)ulong:
          return TK.Integral;
      case cast(__type__)double, cast(__type__)float, 
 cast(__type__)real :
          return TK.Floating;
      default:
          return TK.Other;
   }
 }

 which converts the types into values, the same stuff as 
 integers :)
 and since a switch works with integers it works with types as 
 well.

 And that is precisely the reason why I was so excited about my 
 invention/discovery.

 It just fits in. And requires minimal changes to the spec.

 You will see that I've punted on the definition of the 
 circumstances in which the implicit conversion from type to 
 value and vice versa may occur.
 That's just to keep the post short and avoid many long and 
 boring words ;)

 I do hope you get as excited as me when reading this.

 Regards,
 Stefan
I like this idea quite a lot, some questions/points: 1. How far does this go? Are we just exposing the frontends concept of a type (i.e. the contents of dmd.mtype) or a abstraction over it? Can I (say) make an entirely new type like a struct like I would with a mixin?
I used to think that creating entirely new types was impossible, because the new type would not have a mangle. Now I see that I could just do the same thing as a template would do and give it a mangle of all the arguments to the type functions; that however has the unpleasant side effect of creating potentially giant mangles, which I want to avoid. Therefore the current implementation disallows creation of new types. Also I am not sure how I want the api to look like. Perhaps like this? struct StructField { __type__ type; string name } __type__ makeStruct (string name, StructField[] fields) { __struct__ sresult = __interal__magic__makeStruct(name); foreach(f;fields) { sresult.addField(f.name, f.type); } return __internal__magic__registerType(sresult); }
 2. I don't think avoiding spec changes is necessarily a good 
 thing - D is already fairly murky in that regard so (wrt 
 previous point) specifying exactly what typefunctions can do is 
 probably a good thing.
The are just regular CTFE-only functions they can do anything a pure function can do.
 3. I think a similar thing wouldn't be all that bad for the AST 
 - read-only I should say (the only that worries me in the 
 abstract about that is that dmd's AST isn't very consistent in 
 its API) - i.e. Being able to access it as a regular (say) 
 class at CTFE rather than adding more and more and more 
 __traits.
See the sketch of a possible API above. And feel free to suggest your own!
Jan 01 2021
parent reply Jacob Carlborg <doob me.com> writes:
On 2021-01-02 01:05, Stefan Koch wrote:

 Also I am not sure how I want the api to look like.
 
 Perhaps like this?
 struct StructField
 {
      __type__ type;
      string name
 }
 __type__ makeStruct (string name, StructField[] fields)
 {
      __struct__ sresult = __interal__magic__makeStruct(name);
      foreach(f;fields) { sresult.addField(f.name, f.type); }
      return __internal__magic__registerType(sresult);
 }
I'm not sure if it needs to have an API. Just look at how Zig does it. __type__ LinkedList(__type__ ElementType) { return struct { static struct Node { ElementType element; Node* next; Node* prev; } Node* first; Node* last; } } LinkedList(int) list; If you want to pass in the field names, I guess you'll have to resort to string mixins. Sure, there could be better alternatives than string mixins, but I think that's a separate issue.
 See the sketch of a possible API above.
 
 And feel free to suggest your own!
Here I've experimented in exposing the compiler AST [1]. [1] https://github.com/jacob-carlborg/druntime/blob/517dafcf54ad73049fb35d1ed5fa2ad6619b9ac4/src/core/ast/expression.d -- /Jacob Carlborg
Jan 02 2021
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Saturday, 2 January 2021 at 08:47:07 UTC, Jacob Carlborg wrote:
 On 2021-01-02 01:05, Stefan Koch wrote:

 Also I am not sure how I want the api to look like.
 
 Perhaps like this?
 struct StructField
 {
      __type__ type;
      string name
 }
 __type__ makeStruct (string name, StructField[] fields)
 {
      __struct__ sresult = __interal__magic__makeStruct(name);
      foreach(f;fields) { sresult.addField(f.name, f.type); }
      return __internal__magic__registerType(sresult);
 }
I'm not sure if it needs to have an API. Just look at how Zig does it. __type__ LinkedList(__type__ ElementType) { return struct { static struct Node { ElementType element; Node* next; Node* prev; } Node* first; Node* last; } } LinkedList(int) list;
The issue here is knowing when the type is complete. Imagine you did not put int into there, but some template or another typefunction result. At which point can I take the lock and register myself in the type universe? And how do I make sure I don't have to type LinkedList(f(x, g(y), j(z))) to reference the type? I guess I could create the name to just be "LinkedList(f(x, g(y), j(z)))" where f(x, g(y), j(z))) is eagerly evaluated, and then the name is LinkedList(float) or something like that. But that seems like reinventing templates with all their troubles... perhaps it's still worthwhile though? it's certainly better than a string mixin.
 If you want to pass in the field names, I guess you'll have to 
 resort to string mixins. Sure, there could be better 
 alternatives than string mixins, but I think that's a separate 
 issue.

 See the sketch of a possible API above.
 
 And feel free to suggest your own!
Here I've experimented in exposing the compiler AST [1]. [1] https://github.com/jacob-carlborg/druntime/blob/517dafcf54ad73049fb35d1ed5fa2ad6619b9ac4/src/core/ast/expression.d
Hmm I wanted to avoid having to puzzle ast nodes together, as I find it rather counter verbose to use. simple stuff like; mixin(f(a, b)); becomes new CallExp(findFunction("f"), [findVariableExpression("a"), findVariableExpression("b")].insertIntoTreeInPlace();
Jan 02 2021
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2021-01-02 10:28, Stefan Koch wrote:

 The issue here is knowing when the type is complete.
 Imagine you did not put int into there, but some template or another 
 typefunction result.
 At which point can I take the lock and register myself in the type 
 universe?
 And how do I make sure I don't have to type LinkedList(f(x, g(y), j(z))) 
 to reference the type?
 
 I guess I could create the name to just be "LinkedList(f(x, g(y), 
 j(z)))" where f(x, g(y), j(z))) is eagerly evaluated, and then the name 
 is LinkedList(float) or something like that.
 
 But that seems like reinventing templates with all their troubles...
I don't know. I'm not the expert.
 perhaps it's still worthwhile though?
I like the syntax at least :) Your example doesn't have the same problem because it's explicitly calling `__internal__magic__registerType`?
 Hmm I wanted to avoid having to puzzle ast nodes together, as I find it 
 rather counter verbose to use.
 simple stuff like;
 mixin(f(a, b));
 becomes
 new CallExp(findFunction("f"), [findVariableExpression("a"), 
 findVariableExpression("b")].insertIntoTreeInPlace();
There's nothing that prevents building helper functions on top of the low level AST. In my idea for AST macros, there would be support for quasi-quoting and splicing. It's implemented as a built-in function that converts the internal AST (defined in the compiler) to the external AST (defined in druntime). ast(f(a, b)); // single line ast({ // multiple statements int a = 3; int b = 4; f(a, b); }); auto name = ast(a); ast({ int $name = 3; // splicing. This will declare a variable with the name "a" }); -- /Jacob Carlborg
Jan 06 2021
parent reply sighoya <sighoya gmail.com> writes:
On Wednesday, 6 January 2021 at 13:20:35 UTC, Jacob Carlborg 
wrote:
In my idea for AST macros, there would be support for 
quasi-quoting and splicing. >It's implemented as a built-in 
function that converts the internal AST (defined in the 
compiler) to the external AST (defined in druntime).
ast(f(a, b)); // single line
ast({ // multiple statements
    int a = 3;
    int b = 4;
    f(a, b);
});
auto name = ast(a);
ast({
    int $name = 3; // splicing. This will declare a variable 
 with the name "a"
});
Reminds me on Scala macros. Yes it would be nice, but is it likely to get them in D? If we're allowed to read any source code from our project, we could write our own macro framework to parse strings of code though the work is doubled as they have to be expanded and re-parsed again by the compiler.
Jan 06 2021
parent reply Jacob Carlborg <doob me.com> writes:
On 2021-01-06 14:56, sighoya wrote:

 Reminds me on Scala macros.
Yes, that's the main inspiration. See DIP 50 [1].
 Yes it would be nice, but is it likely to get them in D?
Extremely unlikely.
 If we're allowed to read any source code from our project, we could 
 write our own macro framework to parse strings of code though the work 
 is doubled as they have to be expanded and re-parsed again by the compiler.
Yes, that's possible in theory. DMD could be used as a library to achieve this. The problem is that DMD doesn't contain enough source code location information to do source code refactoring. Also, it doesn't help that the analysis the compiler performs is destructive. https://wiki.dlang.org/DIP50 -- /Jacob Carlborg
Jan 06 2021
parent Jacob Carlborg <doob me.com> writes:
On 2021-01-06 17:35, Jacob Carlborg wrote:
 On 2021-01-06 14:56, sighoya wrote:
  
 Yes it would be nice, but is it likely to get them in D?
Extremely unlikely.
But this conversation is the actual API to the AST. That could be provided with supporting AST macros. -- /Jacob Carlborg
Jan 06 2021
prev sibling parent reply sighoya <sighoya gmail.com> writes:
On Saturday, 2 January 2021 at 09:28:15 UTC, Stefan Koch wrote:
I guess I could create the name to just be "LinkedList(f(x, 
g(y), j(z)))" where f(x, g(y), j(z))) is eagerly evaluated, and 
then the name is LinkedList(float) or something like that.
But that seems like reinventing templates with all their 
troubles...
perhaps it's still worthwhile though?
If we allow a __type__ in type position, indirectly or directly, we're able to implement generics known from other languages, e.g.: ``` method(__type__ type)(type value){...} ``` vs ``` method(T)(T value){...} ``` Although I appreciate a more idiomatic form of generics, we would provide two ways to do the same thing. Moreover, __type__ generics have, as you said, to be eagerly checked, and we are forced to provide full constraints with the potential to forcefully up propagate them. Even though this is the more correct way to set up things it doesn't feel very D-ish or kiss like, don't know if its worth the cost. Theoretically, template systems could provide better errors than generic systems because their error messages would be of concrete kind while those of generics are of abstract kind. The only problem is how to back propagate template error messages such they are meaningful in downstream code, it's possible, but it is a lot of work.
Jan 06 2021
parent reply Q. Schroll <qs.il.paperinik gmail.com> writes:
On Wednesday, 6 January 2021 at 13:52:39 UTC, sighoya wrote:
 If we allow a __type__ in type position, indirectly or 
 directly, we're able to implement generics known from other 
 languages, e.g.:

 ```
 method(__type__ type)(type value){...}
 ```
 vs

 ```
 method(T)(T value){...}
 ```
Is this a suggestion or what you think the semantics' consequences are? Because I fail to see how the first one is any different from a template. If I replace `__type__ type` by `int i`, it is still a compile-time argument. In one instance, the value argument is (or represents) a type. That doesn't change the semantics, or does it?
Jan 12 2021
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Tuesday, 12 January 2021 at 20:41:02 UTC, Q. Schroll wrote:
 On Wednesday, 6 January 2021 at 13:52:39 UTC, sighoya wrote:
 If we allow a __type__ in type position, indirectly or 
 directly, we're able to implement generics known from other 
 languages, e.g.:

 ```
 method(__type__ type)(type value){...}
 ```
 vs

 ```
 method(T)(T value){...}
 ```
Is this a suggestion or what you think the semantics' consequences are? Because I fail to see how the first one is any different from a template. If I replace `__type__ type` by `int i`, it is still a compile-time argument. In one instance, the value argument is (or represents) a type. That doesn't change the semantics, or does it?
A value type can only be used as type if it a statically known value, which in the above example is the case. But I doubt it will work with the ITFI functionality as it is currently implemented. I have never intended this usecase, I think that we should be able to get it to work, but I am not sure if that's the most productive thing to do.
Jan 13 2021
parent reply Max Haughton <maxhaton gmail.com> writes:
On Wednesday, 13 January 2021 at 17:09:59 UTC, Stefan Koch wrote:
 On Tuesday, 12 January 2021 at 20:41:02 UTC, Q. Schroll wrote:
 On Wednesday, 6 January 2021 at 13:52:39 UTC, sighoya wrote:
 If we allow a __type__ in type position, indirectly or 
 directly, we're able to implement generics known from other 
 languages, e.g.:

 ```
 method(__type__ type)(type value){...}
 ```
 vs

 ```
 method(T)(T value){...}
 ```
Is this a suggestion or what you think the semantics' consequences are? Because I fail to see how the first one is any different from a template. If I replace `__type__ type` by `int i`, it is still a compile-time argument. In one instance, the value argument is (or represents) a type. That doesn't change the semantics, or does it?
A value type can only be used as type if it a statically known value, which in the above example is the case. But I doubt it will work with the ITFI functionality as it is currently implemented. I have never intended this usecase, I think that we should be able to get it to work, but I am not sure if that's the most productive thing to do.
I think the usefulness of typefunctions will derive from treating them as an instance of some type like any other. __type__ is conceptually equivalent to having some string mixin of the type it represents. Maybe you shouldn't do so, but a __type__ in a template is just any other template argument - it shouldn't be rewritten to anything else. Stefan, I'm thinking of writing a short document (DIP-lite) to document where I think typefunctions should be specified and used, maybe you could look over it?
Jan 13 2021
parent Stefan Koch <uplink.coder googlemail.com> writes:
On Wednesday, 13 January 2021 at 20:41:38 UTC, Max Haughton wrote:
 On Wednesday, 13 January 2021 at 17:09:59 UTC, Stefan Koch 
 wrote:
 On Tuesday, 12 January 2021 at 20:41:02 UTC, Q. Schroll wrote:
 On Wednesday, 6 January 2021 at 13:52:39 UTC, sighoya wrote:
 If we allow a __type__ in type position, indirectly or 
 directly, we're able to implement generics known from other 
 languages, e.g.:

 ```
 method(__type__ type)(type value){...}
 ```
 vs

 ```
 method(T)(T value){...}
 ```
Is this a suggestion or what you think the semantics' consequences are? Because I fail to see how the first one is any different from a template. If I replace `__type__ type` by `int i`, it is still a compile-time argument. In one instance, the value argument is (or represents) a type. That doesn't change the semantics, or does it?
A value type can only be used as type if it a statically known value, which in the above example is the case. But I doubt it will work with the ITFI functionality as it is currently implemented. I have never intended this usecase, I think that we should be able to get it to work, but I am not sure if that's the most productive thing to do.
I think the usefulness of typefunctions will derive from treating them as an instance of some type like any other. __type__ is conceptually equivalent to having some string mixin of the type it represents. Maybe you shouldn't do so, but a __type__ in a template is just any other template argument - it shouldn't be rewritten to anything else. Stefan, I'm thinking of writing a short document (DIP-lite) to document where I think typefunctions should be specified and used, maybe you could look over it?
Sure I am happy to have a look at your document. One of the reasons that __type__ is not actually a real type. But a type value is because I want typefunctions to be well functions. They should be mono-morphic (Only have one shape). Such that you do not need to generate new function bodies on every call with a different type argument. In order for that to be the case, type arguments must not be able to change the shape of the body. Because if they did, you would be equivalent to templates. (I.E. you cannot proof that the shape does not change from one set of template arguments to the other) Since generation of the multiple shapes + symbols that templates produce such a big strain on the compiler, and compile time; Typefunctions disallow that statically. It also makes it easier to reason about as multiple shapes are hard to keep in ones head.
Jan 14 2021
prev sibling parent sighoya <sighoya gmail.com> writes:
On Tuesday, 12 January 2021 at 20:41:02 UTC, Q. Schroll wrote:
 On Wednesday, 6 January 2021 at 13:52:39 UTC, sighoya wrote:
 If we allow a __type__ in type position, indirectly or 
 directly, we're able to implement generics known from other 
 languages, e.g.:

 ```
 method(__type__ type)(type value){...}
 ```
 vs

 ```
 method(T)(T value){...}
 ```
If I replace `__type__ type` by `int i`, it is still a compile-time argument.
No, it would applicate to: ``` method!(int) ==> demangledMethodInt(int value) {...} ``` Just as for the second case, however, the internals are different.
That doesn't change the semantics, or does it?
I shouldn't except that I think the former part have to be eagerly checked, so you have naturally generics in D. My point was not to introduce this scenario because we have now two ways to do the same thing. Rather, I'm advocating the use of type functions to map types to types only s.t. the ugly parts of D through AliasSeq become obsolete. Type constructors should be served only by templates, we can improve them to behave more like generics for example by providing better error messages or inferred where constraints.
Jan 13 2021
prev sibling parent reply Max Haughton <maxhaton gmail.com> writes:
On Saturday, 2 January 2021 at 08:47:07 UTC, Jacob Carlborg wrote:
 On 2021-01-02 01:05, Stefan Koch wrote:

 [...]
I'm not sure if it needs to have an API. Just look at how Zig does it. __type__ LinkedList(__type__ ElementType) { return struct { static struct Node { ElementType element; Node* next; Node* prev; } Node* first; Node* last; } } LinkedList(int) list; If you want to pass in the field names, I guess you'll have to resort to string mixins. Sure, there could be better alternatives than string mixins, but I think that's a separate issue.
 [...]
Here I've experimented in exposing the compiler AST [1]. [1] https://github.com/jacob-carlborg/druntime/blob/517dafcf54ad73049fb35d1ed5fa2ad6619b9ac4/src/core/ast/expression.d
Did you get as far as actually emitting it to be used during compilation? I'm not about the best way of actually using the AST structure inside the compiler
Jan 02 2021
parent Jacob Carlborg <doob me.com> writes:
On 2021-01-02 20:04, Max Haughton wrote:

 Did you get as far as actually emitting it to be used during 
 compilation? I'm not about the best way of actually using the AST 
 structure inside the compiler
Yes, at least the most basic AST nodes. Converting the internal AST (defined in the compiler) to the external AST (defined in druntime) is the easy part. It's more difficult to do the conversion the other direction. It's a matter of knowing how to construct the internal AST node to make behave the same way as if you had written the corresponding source code. -- /Jacob Carlborg
Jan 06 2021
prev sibling next sibling parent Stefan Koch <uplink.coder googlemail.com> writes:
On Friday, 1 January 2021 at 23:52:58 UTC, Max Haughton wrote:
 [...] specifying exactly what typefunctions can do is probably 
 a good thing.
Let a type tainted value be: - a value of type __type__ or - a value of an aggregate which contains a type value as a field (this is transitive) or - a value which is an associative array that contains a type value. examples are: __type__ x; // x is a type tainted value __type__[string] y; // y is a type tainted value struct S { __type__ x; } S s; // s is a type tainted value s[string] y2; // y2 is a type tainted value class C { typeof(y2) f; } C c = new C(); // c is a type tainted value A typefunction is specified as "a function which either returns a type tainted value, or takes takes at least one type tainted value as argument" A typefunction may only be evaluated at compile time. A typefunction may not generate any code in object files. Other than the 2 typefunction specific rules above it's a regular function. I believe what you want specified are the operations which can be performed on type values. A type value may be created by from types by means of implicit conversion (whenever a type value is expected; such as when calling a type function, or when performing a comparison via is), or a cast to __type__. examples: void f(__type__ x); f(int); // int is implicitly converted to __type__ meaning a the type expression `int` is rewritten to the type value expression `cast(__type__)int`; __type__[__type__] arr; arr = [int:uint, myClass:yourClass]; // here the conversion is inserted as follows arr = [cast(__type__)int:cast(__type__)uint, cast(__type__)myClass:cast(__type__)yourClass]; or explicit auto x = cast(__type__)double;// although that's not needed since the type type is inferred here. meaning: auto x = int; static assert (is(typeof(x) == __type__); // passes A type value exposes all the properties of a type. such as `.sizeof`, `.min`, `.max` and so on. currently the exceptions here are .mangleof and stringof, since they can be referring to the variable itself. examples: __type__ t = int; assert(t.sizeof == 4); // yep t = double; assert(t.sizeof == 8); // works too t = ushort; assert(t.max == ushort.max && t.min == ushort.min); // that's fine assert(t.stringof == ushort.stringof) // fails: t.stringof is "t" Furthermore a type value may be used in an is expression and in __traits which can take a type; where it behaves as if the it were type which it currently represents. struct V3_64 { double x; double y; double z; } t = V3_64; assert(__traits(allMembers, t) == ["x", "y", "z"]); //passes assert(is(t == V3_64)); // passes assert(!is(t == double)); // passes assert(__traits(identifier, t) == "V3_64"); // passes // note: t.stringof is still "t" The initialization value __type__.init is defined as the primitive type __emptyType also known as the empty Type. the emptyType exposes default values for it's properties. sizeof = 0, min = 0, max = 0, __traits(identifier, __type__.init) = __emptyType, __traits(allMemebers, __type__.init) = [] the other specialty about the empty type is that the is expression does not consider it to be a type. which means it can be detected via: is(typeof(t) == __type__) && !is(t); And I that's all there is.
Jan 01 2021
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2021-01-02 00:52, Max Haughton wrote:

 Being  able to access it as a regular (say) class at CTFE rather than adding 
 more and more and more __traits.
That would be nice and not that difficult to do. -- /Jacob Carlborg
Jan 02 2021
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2021-01-02 00:26, Stefan Koch wrote:

 Specification wise type functions require two statements to be added.
 
 "Under certain conditions a type may be implicitly converted to a value 
 of type __type__."
 and
 "Under certain conditions a value of type __type__ may be converted to a 
 type."
"certain conditions" is not suitable for a specification. You need to specify all conditions, all details.
 That's it.
You need to specify what __type__ is as well. A keyword? A special symbol like __FILE__? -- /Jacob Carlborg
Jan 02 2021
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Saturday, 2 January 2021 at 08:31:26 UTC, Jacob Carlborg wrote:
 On 2021-01-02 00:26, Stefan Koch wrote:

 Specification wise type functions require two statements to be 
 added.
 
 "Under certain conditions a type may be implicitly converted 
 to a value of type __type__."
 and
 "Under certain conditions a value of type __type__ may be 
 converted to a type."
"certain conditions" is not suitable for a specification. You need to specify all conditions, all details.
 That's it.
You need to specify what __type__ is as well. A keyword? A special symbol like __FILE__?
Condition for type -> type value : you need to use a type in a place where a type value is needed. Conditions for type value -> type : - you need to use a type value in place where a type is needed AND - the type value needs to be a constant expression. correct __type__ is a basic type just like int, and therefore a keyword. the reason for the four underscores it that __type is already used in the glibc binding.
Jan 02 2021
parent reply sighoya <sighoya gmail.com> writes:
On Saturday, 2 January 2021 at 09:13:42 UTC, Stefan Koch wrote:
 Condition for type -> type value :
   you need to use a type in a place where a type value is 
 needed.

 Conditions for type value -> type :
   - you need to use a type value in place where a type is 
 needed AND
   - the type value needs to be a constant expression.

 correct __type__ is a basic type just like int, and therefore a 
 keyword.
 the reason for the four underscores it that __type is already 
 used in the glibc binding.
At first, thanks for working on first class types. Second, why we need at all __type__ and can't just reuse TypeInfo? TypeInfo already gives us the opportunity to compare types for equality and some other ops. We have `typeid(SecondClassType)` turning a second class type into a first class type, i.e. a value we can do operations on it. We just need the counterpart `asType(FirstClassType)` turning a TypeInfo into an second class type back, this must be done at compile time. And we need to support to call typeid at compile time. Maybe I'm wrong, just want to hear your opinion about it. I appreciate the use of first class types as they allow replacing the ugly nuts of alias seqs and recursion. I think executing a normal function mapping types to types could be faster than macro expansion, though I don't know that exactly.
Jan 02 2021
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Saturday, 2 January 2021 at 14:34:39 UTC, sighoya wrote:
 On Saturday, 2 January 2021 at 09:13:42 UTC, Stefan Koch wrote:
 Condition for type -> type value :
   you need to use a type in a place where a type value is 
 needed.

 Conditions for type value -> type :
   - you need to use a type value in place where a type is 
 needed AND
   - the type value needs to be a constant expression.

 correct __type__ is a basic type just like int, and therefore 
 a keyword.
 the reason for the four underscores it that __type is already 
 used in the glibc binding.
At first, thanks for working on first class types. Second, why we need at all __type__ and can't just reuse TypeInfo? TypeInfo already gives us the opportunity to compare types for equality and some other ops. We have `typeid(SecondClassType)` turning a second class type into a first class type, i.e. a value we can do operations on it. We just need the counterpart `asType(FirstClassType)` turning a TypeInfo into an second class type back, this must be done at compile time. And we need to support to call typeid at compile time. Maybe I'm wrong, just want to hear your opinion about it. I appreciate the use of first class types as they allow replacing the ugly nuts of alias seqs and recursion. I think executing a normal function mapping types to types could be faster than macro expansion, though I don't know that exactly.
The reason I use the new primitive type __type__ is because typeid is meant for RTTI, and it's runtime dependent. Whereas __type__ is a proper langauge builtin. And therefore it can use builtin properties. I would not be able to expose .sizeof as the .sizeof property for example if I used some typeinfo class. In short the look & feel would suffer. By using a less integrated design.
Jan 02 2021
parent reply sighoya <sighoya gmail.com> writes:
On Saturday, 2 January 2021 at 16:29:17 UTC, Stefan Koch wrote:

 The reason I use the new primitive type __type__ is because 
 typeid is meant for RTTI, and it's runtime dependent.
Yes, but I think not for types.
 Whereas __type__ is a proper langauge builtin.
 And therefore it can use builtin properties.
 I would not be able to expose .sizeof as the .sizeof property 
 for example if I used some typeinfo class.
I don't understand this, could we define properties on any type? Or are built-in properties only allowed for built-in types. Note, we have tsized and talign properties for a TypeInfo structure. See: https://dlang.org/library/object/type_info.html
 In short the look & feel would suffer.
Hmm, what about extending TypeInfo to have sizeof and the other properties referring to tsize and talign?
 By using a less integrated design.
My thought is to reuse existing structures.
Jan 02 2021
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Saturday, 2 January 2021 at 17:28:20 UTC, sighoya wrote:
 My thought is to reuse existing structures.
So now in templates you write sizeof and in typefunctions you write tsize? In a template you write alingof and in a typefunction you have to write talign? That's needlessly increasing surface knowledge one needs to have. Yes I can see the appeal but in this case I think it's a misplaced urge to reuse existing structure. The reason that the existing structure typeinfo is not used much at all is because it's less appealing than using the template approach, even if that has other issues.
Jan 02 2021
parent reply sighoya <sighoya gmail.com> writes:
On Saturday, 2 January 2021 at 19:06:32 UTC, Stefan Koch wrote:

That's needlessly increasing surface knowledge one needs to have.
Therefore, I recommended mapping `sizeof` to `tsize` and `alignof` to `talign`. If this isn't possible, then yes you got the point.
 The reason that the existing structure typeinfo is not used 
 much at all is because it's less appealing than using the 
 template approach, even if that has other issues.
Because you can't even do that yet, 1.) we need to lift typeid() to be callable at compile time 2.) and to provide a way to convert a typeinfo back as a second class type, i.e. a normal type we use in templates.
Jan 02 2021
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Saturday, 2 January 2021 at 22:26:32 UTC, sighoya wrote:
 On Saturday, 2 January 2021 at 19:06:32 UTC, Stefan Koch wrote:

That's needlessly increasing surface knowledge one needs to 
have.
Therefore, I recommended mapping `sizeof` to `tsize` and `alignof` to `talign`. If this isn't possible, then yes you got the point.
It isn't possible.
 The reason that the existing structure typeinfo is not used 
 much at all is because it's less appealing than using the 
 template approach, even if that has other issues.
Because you can't even do that yet, 1.) we need to lift typeid() to be callable at compile time 2.) and to provide a way to convert a typeinfo back as a second class type, i.e. a normal type we use in templates.
You can use typeid at compiletime. I know because I implemented it. But TypeInfo is way too incomplete, you don't just need alignment or size, you also need members, UDAs, vtbl layouts and many more. I don't see how one can stuff all that into a statically available, datastructure without blowing out compile times.
Jan 02 2021
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Saturday, 2 January 2021 at 22:47:29 UTC, Stefan Koch wrote:
 I don't see how one can stuff all that into a statically 
 available, datastructure without blowing out compile times.
Build it dynamically using caching (e.g. tables)? That is the standard way of doing unification (e.g. prolog)?
Jan 03 2021
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 3 January 2021 at 11:49:34 UTC, Ola Fosheim Grøstad 
wrote:
 On Saturday, 2 January 2021 at 22:47:29 UTC, Stefan Koch wrote:
 I don't see how one can stuff all that into a statically 
 available, datastructure without blowing out compile times.
Build it dynamically using caching (e.g. tables)? That is the standard way of doing unification (e.g. prolog)?
If I expose it as a 'real' datastructure rather than a langauge builtin I cannot build it dynamically, it needs to be there for use at runtime as well.
Jan 03 2021
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Sunday, 3 January 2021 at 12:06:18 UTC, Stefan Koch wrote:
 If I expose it as a 'real' datastructure rather than a langauge 
 builtin I cannot build it dynamically, it needs to be there for 
 use at runtime as well.
Maybe if you constrain it so that all accessed attributes can be determined at compile time? So, for each compiled d-module you record what information that d-module will try to access at runtime. Then you at the "link stage" generate it?
Jan 03 2021
prev sibling parent reply sighoya <sighoya gmail.com> writes:
On Saturday, 2 January 2021 at 22:47:29 UTC, Stefan Koch wrote:
 You can use typeid at compiletime.
 I know because I implemented it.
``` void main() { import std.algorithm, std.stdio, std.file, std.range; static assert(typeid(int)==typeid(float)); } onlineapp.d(4): Error: static variable typeid(int) cannot be read at compile time onlineapp.d(4): called from here: opEquals(typeid(int), typeid(float)) onlineapp.d(4): while evaluating: static assert(typeid(int) == typeid(float)) ``` Or did you mean you have implemented it in your newCTFE branch?
Jan 03 2021
parent Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 3 January 2021 at 14:05:21 UTC, sighoya wrote:
 On Saturday, 2 January 2021 at 22:47:29 UTC, Stefan Koch wrote:
 You can use typeid at compiletime.
 I know because I implemented it.
``` void main() { import std.algorithm, std.stdio, std.file, std.range; static assert(typeid(int)==typeid(float)); } onlineapp.d(4): Error: static variable typeid(int) cannot be read at compile time onlineapp.d(4): called from here: opEquals(typeid(int), typeid(float)) onlineapp.d(4): while evaluating: static assert(typeid(int) == typeid(float)) ``` Or did you mean you have implemented it in your newCTFE branch?
No, I did not mean that. But the support for typeid in CTFE is not very solid at the moment. Perhaps you can only access certain properties of typeid. The reason being that you cannot generate an actual TypeInfo hence the compiler has to fake it.
Jan 03 2021
prev sibling next sibling parent reply claptrap <clap trap.com> writes:
On Friday, 1 January 2021 at 23:26:58 UTC, Stefan Koch wrote:
 Good Evening and Happy new year to everyone.

 After a stressful move to the UK in uncertain times, I am now 
 free and able to enjoy the last day of my holiday.
Welcome to the UK. Some small advice for the newcomer ;) If someone says "how are you" or "alright", it's a greeting, they dont actually want to know if you are alright. They would be happy with a concise "Im OK", or "Not bad" in response but they dont generally want more than one sentence. An awkward silence after the initial greeting is perfectly acceptable. Almost everything is "Not bad" in the UK, it's not good enough to be excited about anything, but not bad enough to be miserable. The range of life events that fit within the definition of "Not bad" is quite large, family members dying, losing a leg, wining the lottery etc.. are all "Not bad". The weather. Be sure to have an opinion about the weather, it is the most important thing to talk about. Even if you have just seen the person 10 minutes earlier the weather changes so often that you can mention it again. Recently it has become fashionable to say something about the lack of snow in the winter compared with when you were a child, or that it's like summer all year round because of global warming. (British summers are mostly indistinguishable from a mild winter) If in doubt assume British people are being mildly sarcastic whenever they speak. "Spotted dick" is a pudding made with suet and dried fruit.
Jan 02 2021
next sibling parent Stefan Koch <uplink.coder googlemail.com> writes:
On Saturday, 2 January 2021 at 11:59:39 UTC, claptrap wrote:
 On Friday, 1 January 2021 at 23:26:58 UTC, Stefan Koch wrote:
 [...]
Welcome to the UK. Some small advice for the newcomer ;) If someone says "how are you" or "alright", it's a greeting, they dont actually want to know if you are alright. They would be happy with a concise "Im OK", or "Not bad" in response but they dont generally want more than one sentence. [...]
Hmm that wasn't exactly the response I was expecting. But thanks for the advise anyways! Luckily my social contacts are minimal :)
Jan 02 2021
prev sibling parent Per =?UTF-8?B?Tm9yZGzDtnc=?= <per.nordlow gmail.com> writes:
On Saturday, 2 January 2021 at 11:59:39 UTC, claptrap wrote:
...
 "Spotted dick" is a pudding made with suet and dried fruit.
Haha
Jan 08 2021
prev sibling parent jmh530 <john.michael.hall gmail.com> writes:
On Friday, 1 January 2021 at 23:26:58 UTC, Stefan Koch wrote:
 [snip]
 I do hope you get as excited as me when reading this.

 Regards,
 Stefan
Looks interesting.
Jan 02 2021