www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - On type functions

reply Stefan Koch <uplink.coder googlemail.com> writes:
First things first, discussions about syntax are NOT WELCOME in 
this topic!
Type functions are something which I have had floating around my 
mind since early 2018.
While working on newCTFE I did performance measurements on code 
which people send me and which they believed was slow because of 
CTFE.
It turned out that actually recursive introspection templates and 
"unrolled tuple foreach" where actually at faut, most of the time.
Because they littered the code with many many statements and 
symbols that were only temporarily needed at compile time. And 
yet were incorporated into the generated code.

I told people to stay away from templates if they somehow could.
However they could not since introspection capabilities 
(__traits(allMembers) and such) only work if you can work with 
types.
Since templates are the only construct in the language which can 
take type parameters.
You're stuck with them and the associated overhead.

The template overhead:
   - needs to create persistent immutable types to maintain 
language invariants such as (is(T!void == T!void))
   - inherently constraint to strongly pure functional style and 
therefore locked into recursive patterns. (which are inherently 
slow (!) (Even if some language manage to optimize based on the 
stronger guarantees, that optimization itself takes time (which 
we don't have at compile time)))
   - because of the recursive style they are hard to understand 
once they are meant to change behavior based on different types.

About a year ago it struck me that nobody actually _wanted_ to 
use templates, for introspection.
Using templates for metaprogramming is like using a screwdriver 
to hammer nails into a wall.
If you had a hammer you'd use it, but if you don't the 
screwdriver is still a better option than doing it with your bear 
hands.
templates were invented to provide type parameterization not to 
express computation.

Let's take something that was designed to express computation, 
functions, and extend it with the ability to take types as 
objects.
The type function is born. (I thought I came up with it, but 
recently remembered that I first saw it in idris 
(https://www.idris-lang.org/))

D already have a way of expressing a "type variable" the `alias` 
keyword.
so having a function such as

bool isInt(alias T)
{
     return is(T == int);
}

as a completely natural thing to write.

let's now say we wanted to code to serialize a struct to a human 
readable string.
Avoiding templates unnecessary templates. (i.e.) the maximum 
number of template-instances creating during this task should be 
LINEAR to the number of structs printed.

string NameOfField(alias T, size_t fIdx)
{
     assert(is(typeof(T.tupleof), T.stringof ~ " has no .tupleof 
property maybe it's not an aggregate?");
     return __traits(identifier, T.tupleof[fIdx]);
}


string structToString(T)(T struct_)
{

     char[] result;
     result ~= __traits(identifier, T) ~ " :: {\n";
     foreach(fIdx, field; struct_.tupleof)
     {
         result ~= NameOfField!(T, fIdx) ~ " : "; // looks like a 
templates but it's not!

     }
}
May 03
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:
 First things first, discussions about syntax are NOT WELCOME in 
 this topic!
 Type functions are something which I have had floating around 
 my mind since early 2018.
 While working on newCTFE I did performance measurements on code 
 which people send me and which they believed was slow because 
 of CTFE.
 It turned out that actually recursive introspection templates 
 and "unrolled tuple foreach" where actually at faut, most of 
 the time.
 Because they littered the code with many many statements and 
 symbols that were only temporarily needed at compile time. And 
 yet were incorporated into the generated code.

 [...]
The previous post of send before I was finished ... I hit enter by accident. anyways it should already give the gist of what I wanted to convey.
May 03
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 3 May 2020 at 09:37:19 UTC, Stefan Koch wrote:

 The previous post of send before I was finished ... I hit enter 
 by accident.
 anyways it should already give the gist of what I wanted to 
 convey.
Continuation: Type functions supposed to be functions, which means they cannot introduce mutations to global state, such as the number of types known to the compiler. Which automatically means they cannot introduce new types, which are visible outside of the type function itself. If you do want to introduce a new type you can do that having your type function be wrapped in a template which can introduce new types. If there are any other open questions about how this is supposed to work I am happy to answer them.
May 03
next sibling parent reply Panke <tobias pankrath.net> writes:
On Sunday, 3 May 2020 at 10:52:37 UTC, Stefan Koch wrote:
 If there are any other open questions about how this is 
 supposed to work I am happy to answer them.
I think type functions are important work. What operations are available on types? Can I do something like this: --- TStruct structFromSpec(string spec) { TStruct result; for (field; parseFields(spec)) { result.addField(field.name, field.type)? } return result; } --- or how is it supposed to look? I'd figure compile times will be improved greatly at first and suffer heavily afterwards, because everyone will be using this awesome feature to do even more at compile time. Is there any plan to cache the results of type functions? What do we need to incorporate into the design to make it cacheable? I am eager to see this in action.
May 03
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 3 May 2020 at 11:05:13 UTC, Panke wrote:
 On Sunday, 3 May 2020 at 10:52:37 UTC, Stefan Koch wrote:
 If there are any other open questions about how this is 
 supposed to work I am happy to answer them.
I think type functions are important work. What operations are available on types? Can I do something like this: --- TStruct structFromSpec(string spec) { TStruct result; for (field; parseFields(spec)) { result.addField(field.name, field.type)? } return result; } --- or how is it supposed to look? I'd figure compile times will be improved greatly at first and suffer heavily afterwards, because everyone will be using this awesome feature to do even more at compile time. Is there any plan to cache the results of type functions? What do we need to incorporate into the design to make it cacheable? I am eager to see this in action.
struct from spec is exactly one of the things that will not be possible without wrapping it in a template. template structFromString(string spec) { alias structFromStringTypeFn(string spec) { ... } alias = structFromString = structFromStringTypeFn!(spec); } that looks a bit strange but it allows me to not have to worry about certain constraints that I would otherwise have to worry about.
May 03
next sibling parent Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 3 May 2020 at 11:11:03 UTC, Stefan Koch wrote:
 struct from spec is exactly one of the things that will not be 
 possible without wrapping it in a template.

 template structFromString(string spec)
 {
     alias structFromStringTypeFn(string spec)
     {
         ...
     }
     alias = structFromString = structFromStringTypeFn!(spec);
 }
 that looks a bit strange but it allows me to not have to worry 
 about certain constraints that I would otherwise have to worry 
 about.
actually I typed the example out wrong. it's not a good day :-/
May 03
prev sibling parent reply Panke <tobias pankrath.net> writes:
On Sunday, 3 May 2020 at 11:11:03 UTC, Stefan Koch wrote:
 struct from spec is exactly one of the things that will not be 
 possible without wrapping it in a template.

 template structFromString(string spec)
 {
     alias structFromStringTypeFn(string spec)
     {
         ...
     }
     alias = structFromString = structFromStringTypeFn!(spec);
 }
 that looks a bit strange but it allows me to not have to worry 
 about certain constraints that I would otherwise have to worry 
 about.
I have no problem with wrapping it in a template, but want to know what can be done inside of structFromStringTypeFn?
May 03
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 3 May 2020 at 11:19:23 UTC, Panke wrote:
 On Sunday, 3 May 2020 at 11:11:03 UTC, Stefan Koch wrote:
 struct from spec is exactly one of the things that will not be 
 possible without wrapping it in a template.

 template structFromString(string spec)
 {
     alias structFromStringTypeFn(string spec)
     {
         ...
     }
     alias = structFromString = structFromStringTypeFn!(spec);
 }
 that looks a bit strange but it allows me to not have to worry 
 about certain constraints that I would otherwise have to worry 
 about.
I have no problem with wrapping it in a template, but want to know what can be done inside of structFromStringTypeFn?
You can not create ast-fragments or anything like that but you can create a string which when mixed in within the scope of the wrapping template creates the type you want. so structFromStringTypeFn would return a string rather than a type. The only special thing in a type function is that you can use the usual `__traits` and tupleof, on the type parameter. The rest is just like a regular function at ctfe.
May 03
parent victoroak <victoroak victor.com> writes:
On Sunday, 3 May 2020 at 11:23:42 UTC, Stefan Koch wrote:
 
 [...]
 
Have you looked at Zig's comptime and Nim type parameters. It looks like what you want. One thing both Zig and Nim can do that I miss in D is dispatch on whether an argument is known at compile time like a format string without having to use a template parameter. Zig accomplishes both with comptime parameters [1] and having some way to compile time variables would be nice. [1]: https://ziglang.org/documentation/master/#Compile-Time-Parameters
May 03
prev sibling parent Manu <turkeyman gmail.com> writes:
On Sun, May 3, 2020 at 9:10 PM Panke via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 On Sunday, 3 May 2020 at 10:52:37 UTC, Stefan Koch wrote:
 If there are any other open questions about how this is
 supposed to work I am happy to answer them.
I think type functions are important work. What operations are available on types? Can I do something like this: --- TStruct structFromSpec(string spec) { TStruct result; for (field; parseFields(spec)) { result.addField(field.name, field.type)? } return result; } --- or how is it supposed to look? I'd figure compile times will be improved greatly at first and suffer heavily afterwards, because everyone will be using this awesome feature to do even more at compile time. Is there any plan to cache the results of type functions? What do we need to incorporate into the design to make it cacheable? I am eager to see this in action.
Doing "even more" at compile time is not a bad thing, and should actually be perfectly natural. The problem we have today is that recursive instantiation is often N^2, and also very expensive in terms of symbol mangling, and populating the symbol tables with gigabytes of junk. If you remove the N^2-ness and also remove gigabytes of rubbish from the compiler, you can do immensely more then we do now, and still not experience comparable slow-ness to what we experience today.
May 03
prev sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On Sunday, 3 May 2020 at 10:52:37 UTC, Stefan Koch wrote:
 If there are any other open questions about how this is 
 supposed to work I am happy to answer them.
This is really cool! I always thought using recursion is awkward and that we needed something better. I posted about it a long time ago [in a galaxy not too far away]: https://forum.dlang.org/post/mailman.62.1396532327.19942.digitalmars-d puremagic.com The linked post pasted verbatim here: ----- template FilterInts(Args...) { foreach (T; Args) { static if (is(T == int)) FilterInts ~= T; // populate a type tuple } } void main() { static assert(is(FilterInts!(int, float, int, string) == TypeTuple!(int, int))); } ----- Or another one: ----- template GetFirstArray(Args...) { foreach (T; Args) { static if (isArray!T) { GetFirstArray = T; break; } } } void main() { static assert(is(GetFirstArray!(int, int[], float, float[]) == int[])); } ----- But I like the explicitness of `alias` for type functions.
May 04
parent reply Nick Treleaven <nick geany.org> writes:
On Tuesday, 5 May 2020 at 05:03:21 UTC, Andrej Mitrovic wrote:
 template FilterInts(Args...)
 {
     foreach (T; Args)
     {
         static if (is(T == int))
             FilterInts ~= T;  // populate a type tuple
     }
 }
Looks good; I think a declaration above the foreach e.g. `alias[] FilterInts;` would help to make this clearer. Allowing appending to an alias[] in a template seems less of a weighty feature than type functions. The interface to the template and the template result are just normal symbols, i.e. template parameter sequences. This is good for both map and filter, unlike the `T...` expansion proposal, which only does map: https://github.com/dlang/DIPs/blob/956fe8aac9d68a8c8485bd184916faabb0575228/DIPs/DIP10xx.md T... expansion can expand more than one sequence at once, but sequence appending can also do this with an index from foreach: alias Values = AliasSeq!(1, 2, 3); alias Types = AliasSeq!(int, short, float); // alias R = cast(Types)Values...; // a DIP example alias[] R; foreach (i, e; Values) R ~= cast(Types[i])e; Sequence appending is also more flexible because sometimes you need the index to a tuple, not just the element: enum array = [1,0,1]; alias[] Selection; foreach (i, E; Seq) static if (array[i]) Selection ~= E; It would also be nice to be able to swap elements of an alias[], so we can sort it without generating compile-time junk. This might cause implementation complications though, IDK.
May 05
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Tuesday, 5 May 2020 at 12:01:27 UTC, Nick Treleaven wrote:
 On Tuesday, 5 May 2020 at 05:03:21 UTC, Andrej Mitrovic wrote:
 template FilterInts(Args...)
 {
     foreach (T; Args)
     {
         static if (is(T == int))
             FilterInts ~= T;  // populate a type tuple
     }
 }
Looks good; I think a declaration above the foreach e.g. `alias[] FilterInts;` would help to make this clearer. Allowing appending to an alias[] in a template seems less of a weighty feature than type functions. The interface to the template and the template result are just normal symbols, i.e. template parameter sequences. This is good for both map and filter, unlike the `T...` expansion proposal, which only does map: https://github.com/dlang/DIPs/blob/956fe8aac9d68a8c8485bd184916faabb0575228/DIPs/DIP10xx.md T... expansion can expand more than one sequence at once, but sequence appending can also do this with an index from foreach: alias Values = AliasSeq!(1, 2, 3); alias Types = AliasSeq!(int, short, float); // alias R = cast(Types)Values...; // a DIP example alias[] R; foreach (i, e; Values) R ~= cast(Types[i])e; Sequence appending is also more flexible because sometimes you need the index to a tuple, not just the element: enum array = [1,0,1]; alias[] Selection; foreach (i, E; Seq) static if (array[i]) Selection ~= E; It would also be nice to be able to swap elements of an alias[], so we can sort it without generating compile-time junk. This might cause implementation complications though, IDK.
templates are NOT THE RIGHT TOOL. It's a hack. It's ugly. It leads to tons of complications. The implementation of type functions is actually very simple compared to templates. That's why I am going that route rather than putting more strain on the template system which already over-strained because of the constant misuse as computational instrument.
May 05
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, May 05, 2020 at 06:57:04PM +0000, Stefan Koch via Digitalmars-d wrote:
[...]
 templates are  NOT THE RIGHT TOOL.
 It's a hack.
 It's ugly.
 It leads to tons of complications.
 The implementation of type functions is actually very simple compared
 to templates.
 That's why I am going that route rather than putting more strain on
 the template system
 which already over-strained because of the constant misuse as
 computational instrument.
Couldn't agree more! Templates are supposed to be used for parametrization over types (and other compile-time values), but they're not intended for actual *computation*. Just because they happened to be Turing-complete (which is something the C++ people discovered after the fact -- it wasn't designed to be that way), doesn't mean they should actually be used for computations. Abusing templates for computations leads to a litany of well-known symptoms, like exorbitant memory consumption by the compiler, ridiculously-long emitted symbols (remember the bad ole days before Rainer implemented symbol backreferences? A single symbol could be hundreds of KB long), excessively long compile times, and template bloat in the resulting executable. Which probably were a big cause of template haters among the C++ crowd (there are certain minorities of C++ programmers who institute project-wide bans on template use -- some of them would probably turn away from D just by the mere word "template"). Compile-time computation should be done by a proper tool for the job: CTFE, in D's case. Thanks to the D compiler's smartness in interleaving CTFE with AST manipulation, there is no loss of expressive power in doing so; in fact, moving manipulation of type lists aka tuples (whatever they're called these days, after all these years of back and forth we still don't have a good word for them) to CTFE probably makes it even more powerful, because now more complex manipulations are within easy grasp without the exorbitant memory / total compile time cost. tl;dr: I can't wait for newCTFE and typelist/tuple manipulation to land in master. ;-) T -- I've been around long enough to have seen an endless parade of magic new techniques du jour, most of which purport to remove the necessity of thought about your programming problem. In the end they wind up contributing one or two pieces to the collective wisdom, and fade away in the rearview mirror. -- Walter Bright
May 05
next sibling parent 12345swordy <alexanderheistermann gmail.com> writes:
On Tuesday, 5 May 2020 at 19:34:02 UTC, H. S. Teoh wrote:
 On Tue, May 05, 2020 at 06:57:04PM +0000, Stefan Koch via 
 Digitalmars-d wrote: [...]
 templates are  NOT THE RIGHT TOOL.
 It's a hack.
 It's ugly.
 It leads to tons of complications.
 The implementation of type functions is actually very simple 
 compared
 to templates.
 That's why I am going that route rather than putting more 
 strain on
 the template system
 which already over-strained because of the constant misuse as
 computational instrument.
Couldn't agree more! Templates are supposed to be used for parametrization over types (and other compile-time values), but they're not intended for actual *computation*. Just because they happened to be Turing-complete (which is something the C++ people discovered after the fact -- it wasn't designed to be that way), doesn't mean they should actually be used for computations. Abusing templates for computations leads to a litany of well-known symptoms, like exorbitant memory consumption by the compiler, ridiculously-long emitted symbols (remember the bad ole days before Rainer implemented symbol backreferences? A single symbol could be hundreds of KB long), excessively long compile times, and template bloat in the resulting executable. Which probably were a big cause of template haters among the C++ crowd (there are certain minorities of C++ programmers who institute project-wide bans on template use -- some of them would probably turn away from D just by the mere word "template"). Compile-time computation should be done by a proper tool for the job: CTFE, in D's case. Thanks to the D compiler's smartness in interleaving CTFE with AST manipulation, there is no loss of expressive power in doing so; in fact, moving manipulation of type lists aka tuples (whatever they're called these days, after all these years of back and forth we still don't have a good word for them) to CTFE probably makes it even more powerful, because now more complex manipulations are within easy grasp without the exorbitant memory / total compile time cost. tl;dr: I can't wait for newCTFE and typelist/tuple manipulation to land in master. ;-) T
Which hopefully will provide a better syntax then the whole tuples!() aliaseq!() that the phobos library currently provide.
May 05
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Tuesday, 5 May 2020 at 19:34:02 UTC, H. S. Teoh wrote:
 Couldn't agree more!  Templates are supposed to be used for 
 parametrization over types (and other compile-time values), but 
 they're not intended for actual *computation*.  Just because 
 they happened to be Turing-complete (which is something the C++ 
 people discovered after the fact -- it wasn't designed to be 
 that way), doesn't mean they should actually be used for 
 computations.
[...]
 Compile-time computation should be done by a proper tool for 
 the job: CTFE, in D's case.
The most appropriate tool here would be Lisp-style macros. But given Walter's opinion on "the M word", type functions are probably the closest thing we're likely to get.
May 05
prev sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Tuesday, 5 May 2020 at 19:34:02 UTC, H. S. Teoh wrote:
 tl;dr: I can't wait for newCTFE and typelist/tuple manipulation 
 to land in master. ;-)
type function are _significantly_ less work than newCTFE. And they can work within the current CTFE framework. So expect them to land before newCTFE ;)
May 06
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, May 06, 2020 at 12:28:01PM +0000, Stefan Koch via Digitalmars-d wrote:
 On Tuesday, 5 May 2020 at 19:34:02 UTC, H. S. Teoh wrote:
 
 tl;dr: I can't wait for newCTFE and typelist/tuple manipulation to
 land in master. ;-)
 
type function are _significantly_ less work than newCTFE. And they can work within the current CTFE framework. So expect them to land before newCTFE ;)
=-O But wouldn't they need to go through the DIP process first? What's the status on newCTFE btw? What are the major outstanding issues left before it's merge-ready? T -- Маленькие детки - маленькие бедки.
May 06
next sibling parent Stefan Koch <uplink.coder googlemail.com> writes:
On Wednesday, 6 May 2020 at 16:12:59 UTC, H. S. Teoh wrote:
 On Wed, May 06, 2020 at 12:28:01PM +0000, Stefan Koch via 
 Digitalmars-d wrote:
 On Tuesday, 5 May 2020 at 19:34:02 UTC, H. S. Teoh wrote:
 
 tl;dr: I can't wait for newCTFE and typelist/tuple 
 manipulation to land in master. ;-)
 
type function are _significantly_ less work than newCTFE. And they can work within the current CTFE framework. So expect them to land before newCTFE ;)
=-O But wouldn't they need to go through the DIP process first? What's the status on newCTFE btw? What are the major outstanding issues left before it's merge-ready? T
The main issue is that I can think of right now re-throwing exceptions from a catch. There are various others like nested switches and associative arrays.
May 06
prev sibling parent Stefan Koch <uplink.coder googlemail.com> writes:
On Wednesday, 6 May 2020 at 16:12:59 UTC, H. S. Teoh wrote:
 On Wed, May 06, 2020 at 12:28:01PM +0000, Stefan Koch via 
 Digitalmars-d wrote:
 On Tuesday, 5 May 2020 at 19:34:02 UTC, H. S. Teoh wrote:
 
 tl;dr: I can't wait for newCTFE and typelist/tuple 
 manipulation to land in master. ;-)
 
type function are _significantly_ less work than newCTFE. And they can work within the current CTFE framework. So expect them to land before newCTFE ;)
=-O But wouldn't they need to go through the DIP process first? What's the status on newCTFE btw? What are the major outstanding issues left before it's merge-ready? T
type functions are vastly more impactful than newCTFE is. ctfe is lighting-fast when compared to recursive template instantiation.
May 06
prev sibling parent reply Stefan Koch <uplink.coder gmail.com> writes:
On Sunday, 3 May 2020 at 09:37:19 UTC, Stefan Koch wrote:
 The previous post of send before I was finished ... I hit enter 
 by accident.
 anyways it should already give the gist of what I wanted to 
 convey.
Here is how a simple FullyQualifiedName would look when written as type function: --- string fqn(alias t) { char[] result; alias parent; parent = t; while(is(parent) || is(typeof(parent))) { result = __traits(identifier, parent) ~ "." ~ result; parent = __traits(parent, parent); } return cast(string) result; } --- compare that with the phobos version below: --- template fullyQualifiedName(T...) if (T.length == 1) { static if (is(T)) enum fullyQualifiedName = fqnType!(T[0], false, false, false, false); else enum fullyQualifiedName = fqnSym!(T[0]); } private template fqnSym(alias T : X!A, alias X, A...) { template fqnTuple(T...) { static if (T.length == 0) enum fqnTuple = ""; else static if (T.length == 1) { static if (isExpressionTuple!T) enum fqnTuple = T[0].stringof; else enum fqnTuple = fullyQualifiedName!(T[0]); } else enum fqnTuple = fqnTuple!(T[0]) ~ ", " ~ fqnTuple!(T[1 .. $]); } enum fqnSym = fqnSym!(__traits(parent, X)) ~ '.' ~ __traits(identifier, X) ~ "!(" ~ fqnTuple!A ~ ")"; } private template fqnSym(alias T) { static if (__traits(compiles, __traits(parent, T)) && !__traits(isSame, T, __traits(parent, T))) enum parentPrefix = fqnSym!(__traits(parent, T)) ~ "."; else enum parentPrefix = null; static string adjustIdent(string s) { import std.algorithm.searching : findSplit, skipOver; if (s.skipOver("package ") || s.skipOver("module ")) return s; return s.findSplit("(")[0]; } enum fqnSym = parentPrefix ~ adjustIdent(__traits(identifier, T)); } private template fqnType(T, bool alreadyConst, bool alreadyImmutable, bool alreadyShared, bool alreadyInout) { import std.format : format; // Convenience tags enum { _const = 0, _immutable = 1, _shared = 2, _inout = 3 } alias qualifiers = AliasSeq!(is(T == const), is(T == immutable), is(T == shared), is(T == inout)); alias noQualifiers = AliasSeq!(false, false, false, false); string storageClassesString(uint psc)() property { alias PSC = ParameterStorageClass; return format("%s%s%s%s%s", psc & PSC.scope_ ? "scope " : "", psc & PSC.return_ ? "return " : "", psc & PSC.out_ ? "out " : "", psc & PSC.ref_ ? "ref " : "", psc & PSC.lazy_ ? "lazy " : "" ); } string parametersTypeString(T)() property { alias parameters = Parameters!(T); alias parameterStC = ParameterStorageClassTuple!(T); enum variadic = variadicFunctionStyle!T; static if (variadic == Variadic.no) enum variadicStr = ""; else static if (variadic == Variadic.c) enum variadicStr = ", ..."; else static if (variadic == Variadic.d) enum variadicStr = parameters.length ? ", ..." : "..."; else static if (variadic == Variadic.typesafe) enum variadicStr = " ..."; else static assert(0, "New variadic style has been added, please update fullyQualifiedName implementation"); static if (parameters.length) { import std.algorithm.iteration : map; import std.array : join; import std.meta : staticMap; import std.range : zip; string result = join( map!(a => format("%s%s", a[0], a[1]))( zip([staticMap!(storageClassesString, parameterStC)], [staticMap!(fullyQualifiedName, parameters)]) ), ", " ); return result ~= variadicStr; } else return variadicStr; } string linkageString(T)() property { enum linkage = functionLinkage!T; if (linkage != "D") return format("extern(%s) ", linkage); else return ""; } string functionAttributeString(T)() property { alias FA = FunctionAttribute; enum attrs = functionAttributes!T; static if (attrs == FA.none) return ""; else return format("%s%s%s%s%s%s%s%s", attrs & FA.pure_ ? " pure" : "", attrs & FA.nothrow_ ? " nothrow" : "", attrs & FA.ref_ ? " ref" : "", attrs & FA.property ? " property" : "", attrs & FA.trusted ? " trusted" : "", attrs & FA.safe ? " safe" : "", attrs & FA.nogc ? " nogc" : "", attrs & FA.return_ ? " return" : "" ); } string addQualifiers(string typeString, bool addConst, bool addImmutable, bool addShared, bool addInout) { auto result = typeString; if (addShared) { result = format("shared(%s)", result); } if (addConst || addImmutable || addInout) { result = format("%s(%s)", addConst ? "const" : addImmutable ? "immutable" : "inout", result ); } return result; } // Convenience template to avoid copy-paste template chain(string current) { enum chain = addQualifiers(current, qualifiers[_const] && !alreadyConst, qualifiers[_immutable] && !alreadyImmutable, qualifiers[_shared] && !alreadyShared, qualifiers[_inout] && !alreadyInout); } static if (is(T == string)) { enum fqnType = "string"; } else static if (is(T == wstring)) { enum fqnType = "wstring"; } else static if (is(T == dstring)) { enum fqnType = "dstring"; } else static if (isBasicType!T && !is(T == enum)) { enum fqnType = chain!((Unqual!T).stringof); } else static if (isAggregateType!T || is(T == enum)) { enum fqnType = chain!(fqnSym!T); } else static if (isStaticArray!T) { enum fqnType = chain!( format("%s[%s]", fqnType!(typeof(T.init[0]), qualifiers), T.length) ); } else static if (isArray!T) { enum fqnType = chain!( format("%s[]", fqnType!(typeof(T.init[0]), qualifiers)) ); } else static if (isAssociativeArray!T) { enum fqnType = chain!( format("%s[%s]", fqnType!(ValueType!T, qualifiers), fqnType!(KeyType!T, noQualifiers)) ); } else static if (isSomeFunction!T) { static if (is(T F == delegate)) { enum qualifierString = format("%s%s", is(F == shared) ? " shared" : "", is(F == inout) ? " inout" : is(F == immutable) ? " immutable" : is(F == const) ? " const" : "" ); enum formatStr = "%s%s delegate(%s)%s%s"; enum fqnType = chain!( format(formatStr, linkageString!T, fqnType!(ReturnType!T, noQualifiers), parametersTypeString!(T), functionAttributeString!T, qualifierString) ); } else { static if (isFunctionPointer!T) enum formatStr = "%s%s function(%s)%s"; else enum formatStr = "%s%s(%s)%s"; enum fqnType = chain!( format(formatStr, linkageString!T, fqnType!(ReturnType!T, noQualifiers), parametersTypeString!(T), functionAttributeString!T) ); } } else static if (isPointer!T) { enum fqnType = chain!( format("%s*", fqnType!(PointerTarget!T, qualifiers)) ); } else static if (is(T : __vector(V[N]), V, size_t N)) { enum fqnType = chain!( format("__vector(%s[%s])", fqnType!(V, qualifiers), N) ); } else // In case something is forgotten static assert(0, "Unrecognized type " ~ T.stringof ~ ", can't convert to fully qualified string"); } ---
May 07
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 7 May 2020 at 13:26:36 UTC, Stefan Koch wrote:
 Here is how a simple FullyQualifiedName would look when written 
 as type function:
Your implementation won't pass the unit tests... the major complication of Phobos' fullyQualifiedName is reconstructing template arguments which does a *lot* more than __traits(identifier).
May 07
parent Stefan Koch <uplink.coder googlemail.com> writes:
On Thursday, 7 May 2020 at 13:43:36 UTC, Adam D. Ruppe wrote:
 On Thursday, 7 May 2020 at 13:26:36 UTC, Stefan Koch wrote:
 Here is how a simple FullyQualifiedName would look when 
 written as type function:
Your implementation won't pass the unit tests... the major complication of Phobos' fullyQualifiedName is reconstructing template arguments which does a *lot* more than __traits(identifier).
true! But it's a step in the right direction.
May 07
prev sibling next sibling parent Joseph Rushton Wakeling <joseph.wakeling webdrake.net> writes:
On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:
 Let's take something that was designed to express computation, 
 functions, and extend it with the ability to take types as 
 objects.
 The type function is born. (I thought I came up with it, but 
 recently remembered that I first saw it in idris 
 (https://www.idris-lang.org/))
Actually if I remember right you _did_ come up with it independently, and then you spoke with me about it and I said "I think you should look at Idris, it sounds like they are doing something similar..." :-)
May 03
prev sibling next sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:
 Avoiding templates unnecessary templates. (i.e.) the maximum 
 number of template-instances creating during this task should 
 be LINEAR to the number of structs printed.
This trivial with D today, you just do the __traits(identifier) in a perfectly ordinary function inline. More interesting might be pulling UDAs off it too. Like if the name can be overridden: string displayName(alias T)() { string v = __traits(identifier, T); foreach(attr; __traits(getAttributes, T)) static if(is(typeof(attr) == DisplayName)) v = attr.name; return v; } Something like that I do often enough and there is no pretty alternative. You can reduce the number of templates by something like: string nameOrDefault(Attrs...)(string def) { // loop for DisplayName } Which reduces the instantiation to one per unique UDA set; at least the common case of zero simplifies but it still isn't great. The static map proposal in the other thread could help us inline this but still a bit wordy. What's interesting to me is that existing displayName thing really should be optimizable to this already! It is pretty obviously not dependent on any outside state; it is strongly pure (could possibly qualify for the pure keyword too), and it has no runtime arguments, which means it has no runtime branch either. Those two facts together means it should be guaranteed to never be anything other than resolve to the literal return value when executed. Moreover, since it returns a value as opposed to a type, that satisfies your no state in compiler requirement as well. So I'd argue that existing template IS a type function and ought to be trivially recognized as such by the compiler from its existing signature and optimized accordingly - no generation of code, no permanent node generated in the compiler, etc. It gets even easier to recognize if annotated explicitly with `pure` and `assert(__ctfe);` but these are not strictly necessary. The only operation technically allowed now on that which would be illegal would be &(displayName!(args))... and if that happens maybe it could generate it but otherwise it just doesn't.
May 03
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 5/3/20 5:35 AM, Stefan Koch wrote:
 First things first, discussions about syntax are NOT WELCOME in this topic!
 Type functions are something which I have had floating around my mind 
 since early 2018.
 While working on newCTFE I did performance measurements on code which 
 people send me and which they believed was slow because of CTFE.
 It turned out that actually recursive introspection templates and 
 "unrolled tuple foreach" where actually at faut, most of the time.
 Because they littered the code with many many statements and symbols 
 that were only temporarily needed at compile time. And yet were 
 incorporated into the generated code.
 
 I told people to stay away from templates if they somehow could.
 However they could not since introspection capabilities 
 (__traits(allMembers) and such) only work if you can work with types.
 Since templates are the only construct in the language which can take 
 type parameters.
 You're stuck with them and the associated overhead.
 
 The template overhead:
    - needs to create persistent immutable types to maintain language 
 invariants such as (is(T!void == T!void))
    - inherently constraint to strongly pure functional style and 
 therefore locked into recursive patterns. (which are inherently slow (!) 
 (Even if some language manage to optimize based on the stronger 
 guarantees, that optimization itself takes time (which we don't have at 
 compile time)))
    - because of the recursive style they are hard to understand once 
 they are meant to change behavior based on different types.
 
 About a year ago it struck me that nobody actually _wanted_ to use 
 templates, for introspection.
 Using templates for metaprogramming is like using a screwdriver to 
 hammer nails into a wall.
 If you had a hammer you'd use it, but if you don't the screwdriver is 
 still a better option than doing it with your bear hands.
 templates were invented to provide type parameterization not to express 
 computation.
 
 Let's take something that was designed to express computation, 
 functions, and extend it with the ability to take types as objects.
 The type function is born. (I thought I came up with it, but recently 
 remembered that I first saw it in idris (https://www.idris-lang.org/))
 
 D already have a way of expressing a "type variable" the `alias` keyword.
 so having a function such as
 
 bool isInt(alias T)
 {
      return is(T == int);
 }
 
 as a completely natural thing to write.
 
 let's now say we wanted to code to serialize a struct to a human 
 readable string.
 Avoiding templates unnecessary templates. (i.e.) the maximum number of 
 template-instances creating during this task should be LINEAR to the 
 number of structs printed.
 
 string NameOfField(alias T, size_t fIdx)
 {
      assert(is(typeof(T.tupleof), T.stringof ~ " has no .tupleof 
 property maybe it's not an aggregate?");
      return __traits(identifier, T.tupleof[fIdx]);
 }
 
 
 string structToString(T)(T struct_)
 {
 
      char[] result;
      result ~= __traits(identifier, T) ~ " :: {\n";
      foreach(fIdx, field; struct_.tupleof)
      {
          result ~= NameOfField!(T, fIdx) ~ " : "; // looks like a 
 templates but it's not!
 
      }
 }
This is cool, but we can already do this (as Adam says). Would it not be more prudent for this to just be a UDA that says "don't put in symbol table"? What I want is symbol manipulation like variables. i.e.: alias types = AliasSeq!(int, char, bool); alias[] ctSort(alias[] items) { return sort!(t => t.name)(types); } // or something like this static assert(is(ctSort(types) == AliasSeq!(bool, char, int))); I can already do this too, but painfully. -Steve
May 03
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 3 May 2020 at 15:12:32 UTC, Steven Schveighoffer wrote:
 This is cool, but we can already do this (as Adam says). Would 
 it not be more prudent for this to just be a UDA that says 
 "don't put in symbol table"?

 What I want is symbol manipulation like variables.

 i.e.:

 alias types = AliasSeq!(int, char, bool);

 alias[] ctSort(alias[] items) { return sort!(t => 
 t.name)(types); } // or something like this

 static assert(is(ctSort(types) == AliasSeq!(bool, char, int)));

 I can already do this too, but painfully.

 -Steve
alias[] is something that comes with type functions. when returned an alias[] becomes a tuple. But within a type function an alias[] can be assigned to. And "~=" works for it.
May 03
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 5/3/20 11:28 AM, Stefan Koch wrote:
 On Sunday, 3 May 2020 at 15:12:32 UTC, Steven Schveighoffer wrote:
 This is cool, but we can already do this (as Adam says). Would it not 
 be more prudent for this to just be a UDA that says "don't put in 
 symbol table"?

 What I want is symbol manipulation like variables.

 i.e.:

 alias types = AliasSeq!(int, char, bool);

 alias[] ctSort(alias[] items) { return sort!(t => t.name)(types); } // 
 or something like this

 static assert(is(ctSort(types) == AliasSeq!(bool, char, int)));

 I can already do this too, but painfully.
alias[] is something that comes with type functions. when returned an alias[] becomes a tuple. But within a type function an alias[] can be assigned to. And "~=" works for it.
OK. You should have lead with that ;) Looking forward to it. -Steve
May 03
prev sibling next sibling parent jmh530 <john.michael.hall gmail.com> writes:
On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:
 [snip]
I'm not sure if this is exactly the same thing, but it reminded me of Luis Marques' talk at DConf 2019 [1] where he discusses first class types and functions that return types. [1] http://dconf.org/2019/talks/marques.html
May 03
prev sibling next sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:

 string NameOfField(alias T, size_t fIdx)
 {
     assert(is(typeof(T.tupleof), T.stringof ~ " has no .tupleof 
 property maybe it's not an aggregate?");
     return __traits(identifier, T.tupleof[fIdx]);
 }


 string structToString(T)(T struct_)
 {

     char[] result;
     result ~= __traits(identifier, T) ~ " :: {\n";
     foreach(fIdx, field; struct_.tupleof)
     {
         result ~= NameOfField!(T, fIdx) ~ " : "; // looks like 
 a templates but it's not!

     }
 }
Cool! Does it mean the following will work? alias[] map(alias f, alias[] a) { alias[] r; foreach (e; a) r ~= f!e; // can we? return r; } enum isType(alias a) = is(a); static assert([map!(isType, AliasSeq!(1, int, "meh"))] == [false, true, false]);
May 03
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote:
 On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:

 [...]
Cool! Does it mean the following will work? alias[] map(alias f, alias[] a) { alias[] r; foreach (e; a) r ~= f!e; // can we? return r; } enum isType(alias a) = is(a); static assert([map!(isType, AliasSeq!(1, int, "meh"))] == [false, true, false]);
Yes that's what it means!
May 03
next sibling parent Max Samukha <maxsamukha gmail.com> writes:
On Sunday, 3 May 2020 at 21:22:19 UTC, Stefan Koch wrote:
 On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote:
 On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:

 [...]
Cool! Does it mean the following will work? alias[] map(alias f, alias[] a) { alias[] r; foreach (e; a) r ~= f!e; // can we? return r; } enum isType(alias a) = is(a); static assert([map!(isType, AliasSeq!(1, int, "meh"))] == [false, true, false]);
Yes that's what it means!
If you can pull that off, it will be amazing.
May 03
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 5/3/20 5:22 PM, Stefan Koch wrote:
 On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote:
 On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:

 [...]
Cool! Does it mean the following will work? alias[] map(alias f, alias[] a) {    alias[] r;    foreach (e; a)        r ~= f!e; // can we?    return r; } enum isType(alias a) = is(a); static assert([map!(isType, AliasSeq!(1, int, "meh"))] == [false, true, false]);
Yes that's what it means!
I had thought this might work, but I'm not sure. Today AliasSeq parameters can bind to normal parameter lists. If you have e.g. AliasSeq!(1, 2, 3), will that bind to an alias[]? Will you be able to overload with a function that takes 3 ints? I'm supposing that it should work, and that if you have a "more specific" overload, it would use that instead. One other thing that is unclear is that map is supposed to take it's list *first* for UFCS. This would have to be different, as variadics typically come at the end of argument lists. -Steve
May 04
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 4 May 2020 at 15:42:01 UTC, Steven Schveighoffer wrote:
 On 5/3/20 5:22 PM, Stefan Koch wrote:
 On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote:
 On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:

 [...]
Cool! Does it mean the following will work? alias[] map(alias f, alias[] a) {    alias[] r;    foreach (e; a)        r ~= f!e; // can we?    return r; } enum isType(alias a) = is(a); static assert([map!(isType, AliasSeq!(1, int, "meh"))] == [false, true, false]);
Yes that's what it means!
I had thought this might work, but I'm not sure.
[...]
 One other thing that is unclear is that map is supposed to take 
 it's list *first* for UFCS. This would have to be different, as 
 variadics typically come at the end of argument lists.

 -Steve
If I understand the proposal correctly, it should be ok handle UFCS the same way std.algorithm.map does, by taking the function as a template argument: auto map(alias f)(alias[] a) { // etc }
May 04
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Monday, 4 May 2020 at 15:55:44 UTC, Paul Backus wrote:
 On Monday, 4 May 2020 at 15:42:01 UTC, Steven Schveighoffer 
 wrote:
 On 5/3/20 5:22 PM, Stefan Koch wrote:
 On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote:
 [...]
Yes that's what it means!
I had thought this might work, but I'm not sure.
[...]
 One other thing that is unclear is that map is supposed to 
 take it's list *first* for UFCS. This would have to be 
 different, as variadics typically come at the end of argument 
 lists.

 -Steve
If I understand the proposal correctly, it should be ok handle UFCS the same way std.algorithm.map does, by taking the function as a template argument: auto map(alias f)(alias[] a) { // etc }
In my current draft type functions are not overload-able. And having UFCS work was never really something I thought about. I would like to work on a version which does not take UFCS just as a first step.
May 04
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 04.05.20 19:09, Stefan Koch wrote:
 On Monday, 4 May 2020 at 15:55:44 UTC, Paul Backus wrote:
 On Monday, 4 May 2020 at 15:42:01 UTC, Steven Schveighoffer wrote:
 On 5/3/20 5:22 PM, Stefan Koch wrote:
 On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote:
 [...]
Yes that's what it means!
I had thought this might work, but I'm not sure.
[...]
 One other thing that is unclear is that map is supposed to take it's 
 list *first* for UFCS. This would have to be different, as variadics 
 typically come at the end of argument lists.

 -Steve
If I understand the proposal correctly, it should be ok handle UFCS the same way std.algorithm.map does, by taking the function as a template argument: auto map(alias f)(alias[] a) {     // etc }
In my current draft type functions are not overload-able. And having UFCS work was never really something I thought about. I would like to work on a version which does not take UFCS just as a first step.
That's a bit concerning. Why would overloading and UFCS require a separate implementation for type functions and functions that do not operate on types?
May 06
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Wednesday, 6 May 2020 at 14:28:20 UTC, Timon Gehr wrote:
 On 04.05.20 19:09, Stefan Koch wrote:
 On Monday, 4 May 2020 at 15:55:44 UTC, Paul Backus wrote:
 On Monday, 4 May 2020 at 15:42:01 UTC, Steven Schveighoffer 
 wrote:
 [...]
[...]
 [...]
If I understand the proposal correctly, it should be ok handle UFCS the same way std.algorithm.map does, by taking the function as a template argument: auto map(alias f)(alias[] a) {     // etc }
In my current draft type functions are not overload-able. And having UFCS work was never really something I thought about. I would like to work on a version which does not take UFCS just as a first step.
That's a bit concerning. Why would overloading and UFCS require a separate implementation for type functions and functions that do not operate on types?
ufcs doesn't work merely because of how it parses right now. I cannot really see where overloading a type function would make sense. If you have a compelling example where it's useful. I may reconsider.
May 06
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06.05.20 16:40, Stefan Koch wrote:
 On Wednesday, 6 May 2020 at 14:28:20 UTC, Timon Gehr wrote:
 On 04.05.20 19:09, Stefan Koch wrote:
 On Monday, 4 May 2020 at 15:55:44 UTC, Paul Backus wrote:
 On Monday, 4 May 2020 at 15:42:01 UTC, Steven Schveighoffer wrote:
 [...]
[...]
 [...]
If I understand the proposal correctly, it should be ok handle UFCS the same way std.algorithm.map does, by taking the function as a template argument: auto map(alias f)(alias[] a) {     // etc }
In my current draft type functions are not overload-able. And having UFCS work was never really something I thought about. I would like to work on a version which does not take UFCS just as a first step.
That's a bit concerning. Why would overloading and UFCS require a separate implementation for type functions and functions that do not operate on types?
ufcs doesn't work merely because of how it parses right now. I cannot really see where overloading a type function would make sense.
My question was, why is there a separate implementation in the first place? Type functions are just functions that take at least one parameter that includes an 'alias' type. There does not seem to be any reason why it should be harder to have it working than to not have it working.
 If you have a compelling example where it's useful. I may reconsider.
The compelling example was given already. Use std.algorithm with ranges of types. In any case, this is not how language design works. Independent language features are _supposed_ to be freely combined by programmers and if they can't be that's a failing on part of the language designer and the "I didn't think it was useful" defense is nonsense, because if you implement your compiler properly it is easier to not have arbitrary restrictions than it is to have them.
May 07
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Thursday, 7 May 2020 at 17:47:11 UTC, Timon Gehr wrote:
 If you implement your compiler properly it is easier to not 
 have arbitrary restrictions than it is to have them.
I work with the DMD as basis. The less I have to touch the parser, the better.
May 07
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 07.05.20 19:51, Stefan Koch wrote:
 On Thursday, 7 May 2020 at 17:47:11 UTC, Timon Gehr wrote:
 If you implement your compiler properly it is easier to not have 
 arbitrary restrictions than it is to have them.
I work with the DMD as basis. The less I have to touch the parser, the better.
Apparently the newsgroup dropped my answer to this post, so I'll try again: It's clear that you work with DMD as the basis, but my question was simply _what_ the design mistake is that causes this sort of murky incompatibility of features. I don't see how the parser has anything to do with it, as UFCS/overloads are (hopefully) resolved in semantic.
May 07
parent Stefan Koch <uplink.coder googlemail.com> writes:
On Friday, 8 May 2020 at 02:43:09 UTC, Timon Gehr wrote:
 On 07.05.20 19:51, Stefan Koch wrote:
 On Thursday, 7 May 2020 at 17:47:11 UTC, Timon Gehr wrote:
 If you implement your compiler properly it is easier to not 
 have arbitrary restrictions than it is to have them.
I work with the DMD as basis. The less I have to touch the parser, the better.
Apparently the newsgroup dropped my answer to this post, so I'll try again: It's clear that you work with DMD as the basis, but my question was simply _what_ the design mistake is that causes this sort of murky incompatibility of features. I don't see how the parser has anything to do with it, as UFCS/overloads are (hopefully) resolved in semantic.
The process of "calling" a type function is different from a regular function call since the arguments have to go through a conversion step before they can be given to the type function. That's probably fixable but I don't know how much time this will take. Currently I am implementing a "quick" Proof of Concept to show the merits of this approach. Therefore I am not thinking too hard about overloading or similar matters which are tangential to showing the essential benefit of a dedicated construct for type based decisions and computation.
May 10
prev sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote:
 On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:
 alias[] map(alias f, alias[] a)
Or should it be 'alias[] map(alias f, alias[] a...)' in consistency with type safe variadic functions?
May 03
parent Stefan Koch <uplink.coder googlemail.com> writes:
On Sunday, 3 May 2020 at 21:23:17 UTC, Max Samukha wrote:
 On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote:
 On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:
 alias[] map(alias f, alias[] a)
Or should it be 'alias[] map(alias f, alias[] a...)' in consistency with type safe variadic functions?
I haven't decided currently I am leaning to the way you wrote it first.
May 03
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:

 The type function is born. (I thought I came up with it, but 
 recently remembered that I first saw it in idris 
 (https://www.idris-lang.org/))
It actually exists in several other languages: * Zig * Ruby * Objective-C (kind of) In Ruby, everything is an object, including classes. Since also primitive types are objects it natural falls out that Ruby have first class types. class Foo end class Bar end cls = ARGV[0] == 'bar' ? Bar : Foo b = cls.new
 D already have a way of expressing a "type variable" the 
 `alias` keyword.
 so having a function such as

 bool isInt(alias T)
 {
     return is(T == int);
 }

 as a completely natural thing to write.
My biggest issue with this has not been performance but instead the one needs to write the code in a completely different style (recursive templates and there are many corner cases). I think it's important that the existing algorithms (std.algorithm) works with types. Looking at the example in one of the other posts in this thread [1]: alias[] map(alias f, alias[] a) { alias[] r; foreach (e; a) r ~= f!e; // can we? return r; } enum isType(alias a) = is(a); static assert([map!(isType, AliasSeq!(1, int, "meh"))] == [false, true, false]); I think it would be very unfortunate if the existing algorithms didn't work and they need to be recreated. Then we're back to what we have today, like `staticMap` and `Filter`, (perhaps just with a different implementation). I want full support for first class types. Not just limited to some specific functions. This should be able to work: import std.algorithm; import std.array; static assert([int, char, long].map!(a => a.stringof).array == ["int", "char", "long"]); But I guess "type functions" are better than nothing. [1] https://forum.dlang.org/post/jaobevgsoxqibieuayih forum.dlang.org -- /Jacob Carlborg
May 04
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 5/4/20 8:37 AM, Jacob Carlborg wrote:
 I think it would be very unfortunate if the existing algorithms didn't 
 work and they need to be recreated. Then we're back to what we have 
 today, like `staticMap` and `Filter`, (perhaps just with a different 
 implementation).
No, staticMap has to be different because it accepts its parameters via compile-time parameter list and called differently. If using alias in the normal parameter list is acceptable, then it just becomes another overload. I think you still need a separate implementation due to the mechanisms needed for "storing" aliases. But the syntax should be identical to what you wrote. -Steve
May 04