Jump to page: 1 24  
Page
Thread overview
January 01
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
January 01
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?
January 01
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.
January 01
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.
January 02
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!

January 02
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.
January 02
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
January 02
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
January 02
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
January 02
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
« First   ‹ Prev
1 2 3 4