digitalmars.D - On type functions
- Stefan Koch (73/73) May 03 2020 First things first, discussions about syntax are NOT WELCOME in
- Stefan Koch (5/19) May 03 2020 The previous post of send before I was finished ... I hit enter
- Stefan Koch (12/16) May 03 2020 Continuation:
- Panke (22/24) May 03 2020 I think type functions are important work.
- Stefan Koch (14/39) May 03 2020 struct from spec is exactly one of the things that will not be
- Stefan Koch (3/16) May 03 2020 actually I typed the example out wrong.
- Panke (3/16) May 03 2020 I have no problem with wrapping it in a template, but want to
- Stefan Koch (10/28) May 03 2020 You can not create ast-fragments or anything like that but you
- victoroak (10/13) May 03 2020 Have you looked at Zig's comptime and Nim type parameters. It
- Manu (10/35) May 03 2020 Doing "even more" at compile time is not a bad thing, and should actuall...
- Andrej Mitrovic (42/44) May 04 2020 This is really cool!
- Nick Treleaven (28/36) May 05 2020 Looks good; I think a declaration above the foreach e.g. `alias[]
- Stefan Koch (11/47) May 05 2020 templates are NOT THE RIGHT TOOL.
- H. S. Teoh (30/40) May 05 2020 Couldn't agree more! Templates are supposed to be used for
- 12345swordy (3/48) May 05 2020 Which hopefully will provide a better syntax then the whole
- Paul Backus (5/14) May 05 2020 The most appropriate tool here would be Lisp-style macros. But
- Stefan Koch (4/6) May 06 2020 type function are _significantly_ less work than newCTFE.
- H. S. Teoh (8/18) May 06 2020 =-O
- Stefan Koch (5/22) May 06 2020 The main issue is that I can think of right now re-throwing
- Stefan Koch (4/21) May 06 2020 type functions are vastly more impactful than newCTFE is.
- Stefan Koch (274/278) May 07 2020 Here is how a simple FullyQualifiedName would look when written
- Adam D. Ruppe (5/7) May 07 2020 Your implementation won't pass the unit tests... the major
- Stefan Koch (2/9) May 07 2020 true! But it's a step in the right direction.
- Joseph Rushton Wakeling (5/11) May 03 2020 Actually if I remember right you _did_ come up with it
- Adam D. Ruppe (41/44) May 03 2020 This trivial with D today, you just do the __traits(identifier)
- Steven Schveighoffer (12/91) May 03 2020 This is cool, but we can already do this (as Adam says). Would it not be...
- Stefan Koch (5/16) May 03 2020 alias[] is something that comes with type functions.
- Steven Schveighoffer (4/28) May 03 2020 OK. You should have lead with that ;)
- jmh530 (5/6) May 03 2020 I'm not sure if this is exactly the same thing, but it reminded
- Max Samukha (11/27) May 03 2020 Cool! Does it mean the following will work?
- Stefan Koch (2/14) May 03 2020 Yes that's what it means!
- Max Samukha (2/21) May 03 2020 If you can pull that off, it will be amazing.
- Steven Schveighoffer (11/31) May 04 2020 I had thought this might work, but I'm not sure.
- Paul Backus (8/34) May 04 2020 If I understand the proposal correctly, it should be ok handle
- Stefan Koch (5/27) May 04 2020 In my current draft type functions are not overload-able.
- Timon Gehr (4/32) May 06 2020 That's a bit concerning. Why would overloading and UFCS require a
- Stefan Koch (6/30) May 06 2020 ufcs doesn't work merely because of how it parses right now.
- Timon Gehr (13/41) May 07 2020 My question was, why is there a separate implementation in the first
- Stefan Koch (3/5) May 07 2020 I work with the DMD as basis.
- Timon Gehr (6/13) May 07 2020 Apparently the newsgroup dropped my answer to this post, so I'll try aga...
- Stefan Koch (11/26) May 10 2020 The process of "calling" a type function is different from a
- Max Samukha (3/5) May 03 2020 Or should it be 'alias[] map(alias f, alias[] a...)' in
- Stefan Koch (3/9) May 03 2020 I haven't decided currently I am leaning to the way you wrote it
- Jacob Carlborg (48/59) May 04 2020 It actually exists in several other languages:
- Steven Schveighoffer (9/13) May 04 2020 No, staticMap has to be different because it accepts its parameters via
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 2020
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 2020
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 2020
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 2020
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: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.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 2020
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 2020
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 2020
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: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.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 2020
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 2020
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: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.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 2020
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 2020
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 2020
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: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.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 2020
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 2020
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: [...]Which hopefully will provide a better syntax then the whole tuples!() aliaseq!() that the phobos library currently provide.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
May 05 2020
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 2020
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 2020
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:=-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 -- Маленькие детки - маленькие бедки.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 2020
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: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.On Tuesday, 5 May 2020 at 19:34:02 UTC, H. S. Teoh wrote:=-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? Ttl;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 2020
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:type functions are vastly more impactful than newCTFE is. ctfe is lighting-fast when compared to recursive template instantiation.On Tuesday, 5 May 2020 at 19:34:02 UTC, H. S. Teoh wrote:=-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? Ttl;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 2020
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 2020
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 2020
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:true! But it's a step in the right direction.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 2020
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 2020
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 2020
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 2020
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. -Stevealias[] 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 2020
On 5/3/20 11:28 AM, Stefan Koch wrote:On Sunday, 3 May 2020 at 15:12:32 UTC, Steven Schveighoffer wrote:OK. You should have lead with that ;) Looking forward to it. -SteveThis 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.
May 03 2020
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 2020
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 2020
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:Yes that's what it means![...]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 2020
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:If you can pull that off, it will be amazing.On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:Yes that's what it means![...]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 2020
On 5/3/20 5:22 PM, Stefan Koch wrote:On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote: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. -SteveOn Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:Yes that's what it means![...]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 04 2020
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:I had thought this might work, but I'm not sure.On Sunday, 3 May 2020 at 09:35:34 UTC, Stefan Koch wrote:Yes that's what it means![...]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]);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. -SteveIf 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 2020
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: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.On 5/3/20 5:22 PM, Stefan Koch wrote:[...]On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote:I had thought this might work, but I'm not sure.[...]Yes that's what it means!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. -SteveIf 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 2020
On 04.05.20 19:09, Stefan Koch wrote:On Monday, 4 May 2020 at 15:55:44 UTC, Paul Backus wrote: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?On Monday, 4 May 2020 at 15:42:01 UTC, Steven Schveighoffer wrote: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.On 5/3/20 5:22 PM, Stefan Koch wrote:[...]On Sunday, 3 May 2020 at 21:14:43 UTC, Max Samukha wrote:I had thought this might work, but I'm not sure.[...]Yes that's what it means!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. -SteveIf 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 06 2020
On Wednesday, 6 May 2020 at 14:28:20 UTC, Timon Gehr wrote:On 04.05.20 19:09, Stefan Koch wrote: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.On Monday, 4 May 2020 at 15:55:44 UTC, Paul Backus wrote: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?On Monday, 4 May 2020 at 15:42:01 UTC, Steven Schveighoffer wrote: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.[...][...][...]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 06 2020
On 06.05.20 16:40, Stefan Koch wrote:On Wednesday, 6 May 2020 at 14:28:20 UTC, Timon Gehr wrote: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.On 04.05.20 19:09, Stefan Koch wrote:ufcs doesn't work merely because of how it parses right now. I cannot really see where overloading a type function would make sense.On Monday, 4 May 2020 at 15:55:44 UTC, Paul Backus wrote: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?On Monday, 4 May 2020 at 15:42:01 UTC, Steven Schveighoffer wrote: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.[...][...][...]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 }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 2020
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 2020
On 07.05.20 19:51, Stefan Koch wrote:On Thursday, 7 May 2020 at 17:47:11 UTC, Timon Gehr wrote: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.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 2020
On Friday, 8 May 2020 at 02:43:09 UTC, Timon Gehr wrote:On 07.05.20 19:51, Stefan Koch wrote: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.On Thursday, 7 May 2020 at 17:47:11 UTC, Timon Gehr wrote: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.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 10 2020
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 2020
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:I haven't decided currently I am leaning to the way you wrote it first.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 2020
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.newD 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 2020
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 2020