January 13, 2021
On Tuesday, 12 January 2021 at 14:45:36 UTC, Adam D. Ruppe wrote:
> A Java generic doesn't generate new code for other types at all. It is just a runtime class that works in terms of interfaces, and when the compiler parameterizes it, it basically just inserts static casts at the interface boundary for you.
>
> The benefit of this is you have almost zero compile time cost and runtime cost comparable to any other class. It avoids template bloat in codegen that can be very significant in D.
>
> I'd love to have it as an option in D as well. There's a lot of types that can use identical runtime code and just change types. Not just classes, but like integer types too can be identical and merged, const/immutable/shared/etc can be identical and merged, and even other things with cast(void*) can do it.

There was a recent paper about doing this kind of transformation automatically as an optimization - https://www.microsoft.com/en-us/research/uploads/prod/2020/03/kacc.pdf
January 13, 2021
On Tuesday, 12 January 2021 at 14:45:36 UTC, Adam D. Ruppe wrote:
>
> The benefit of this is you have almost zero compile time cost and runtime cost comparable to any other class. It avoids template bloat in codegen that can be very significant in D.
>
> I'd love to have it as an option in D as well. There's a lot of types that can use identical runtime code and just change types. Not just classes, but like integer types too can be identical and merged, const/immutable/shared/etc can be identical and merged, and even other things with cast(void*) can do it.
>

A practice I've heard from game industry people was to instantiate template with void* to avoid duplicate template instantiation, and then cast at boundaries. Typically for containers.
January 13, 2021
On Wednesday, 13 January 2021 at 08:19:25 UTC, Elronnd wrote:
>
> There was a recent paper about doing this kind of transformation automatically as an optimization - https://www.microsoft.com/en-us/research/uploads/prod/2020/03/kacc.pdf

Thanks for the link.  The paper looks like a major advance, one that should strongly influence future compiler architectures.





January 13, 2021
Go now has a generics proposal:

https://github.com/golang/go/issues/43651

January 13, 2021
On Monday, 11 January 2021 at 18:23:20 UTC, Q. Schroll wrote:
> The only programming language I know of that has both, templates and generics, is C++/CLI and ones closely related.
>
> I wonder, why D has classes similar to C# and Java, but does not have generics.

Because OOP could be rarely supported in D without OOP and pure template programming while templates already serve the need for generics excluding the complexity they are introducing.


> A generic class or interface states its requirements (base classes, interfaces, [never seen in the wild:] subclasses, ...) to its type parameters exactly.

D can already do that with where constraints.

> Everything that is part of the implementation is checked when the generic aggregate is defined.

So simply speaking you seek for eagerly checked templates which indeed seems to be
 an advantage of generics but why not optionally the same for templates?

Rather reinventing the wheel, why not modulating the template error system to infer and uppropagate where constraints automatically by traversing type/function bodies?

Another issue of generics are the parametrized error messages often disliked by people using them.
We could do that, in theory, better just by mentioning the position of the occurred error in the template-expanded code fragment similar to how Python handles type errors.
It would remove the barrier of template utilization a lot.


> Generics allow for implicit conversion by covariance and contravariance. (C# [1]) Generics allow for reducing an interface to its covariant or contravariant part-interface (that is a supertype of the interface, really). (Java [2, 3])

I don't see any reason why not providing them with templates, too with specialized syntax for example by:

//Covariance
dlist!(T where T:A || T:B) ==> dlist!(Algebraic!(A,B))

//Contravariance
dlist!(T where A:T) ==> dlist!(Object)//where compiler rejects any assignment of A's strict subtypes.

> Using templates, the compiler checks requirements only for specific instances. It might be that the requirements are insufficient, but because no test tried the potentially very specific type argument, it will be unrecognized.

Yep, sorta implicit where constraints would serve the purpose here.

>
> Also, one feature D doesn't have, is expressing the precise union or intersection of interfaces: Say you have two interfaces I and J.

Isn't a question about templates rather about intersection and union types as language level concept or as library solution.

> The interface (I & J) has all the methods specified by any of them. A class that implements I and J automatically implements (I & J).
> The interface (I | J) has all the methods specified by both of them. A class that implements I or implements J automatically implements (I | J). If you e.g. iterate an (I | J)[] object, you can call any method required by both interfaces. (Typescript [4])
> It might be hard or even impossible to implement this using vtables and stuff.
>
Personally, I think it can with kinda implicitCoercionOp and Algebraic and some Intersection like variant.

> In D, one can easily create templates intersectInterface(Is...) and unionInterface(Is...) that basically do that.
>
> It could very well be that D doesn't have them because they have to be implemented and maintained, and the cost/benefit ratio wasn't good enough.

D has already Algebraic for Unions, don't know about Intersection implementations.

Note that the implementation for Unions vary. In Scala (and proposed for C#) they are purely class/interface based, i.e. the union of two classes is internally just the supertype of both classes and the intersection of interfaces are classes/interfaces implementing/extending all the said interfaces which could implemented with some support of compile time reflection for OOP hierarchies.
It has however the disadvantage of overloading ambiguities when two pairs of classes have the same supertype.

But I would prefer to support other types than classes and interfaces as well just as it is the case with Algebraic.

January 14, 2021
On Wednesday, 13 January 2021 at 08:19:25 UTC, Elronnd wrote:
> On Tuesday, 12 January 2021 at 14:45:36 UTC, Adam D. Ruppe wrote:
>> [...]
>
> There was a recent paper about doing this kind of transformation automatically as an optimization - https://www.microsoft.com/en-us/research/uploads/prod/2020/03/kacc.pdf

Nice paper 📄

(Well, the contents of the paper. Which is not actual paper.. *IQ increases*)
January 15, 2021
On Wednesday, 13 January 2021 at 08:33:58 UTC, Guillaume Piolat wrote:
> A practice I've heard from game industry people was to instantiate template with void* to avoid duplicate template instantiation, and then cast at boundaries. Typically for containers.

Yes, that is what I suggested also. I implemented it yesterday for deque, stack, etc, all using the same memory layout for 64 bit values (pointers, ints, doubles etc).

What makes it cool is that you can construct datastructures that use the same ranges code, despite having different types.

However, it cannot be done without very negative effects on precise collection, I think. So it is a bad fit for D, since it is GC based, and precise collection is the future...

So, trashed it. (I translated it to C++ instead... :-P)


January 15, 2021
On Friday, 15 January 2021 at 11:58:13 UTC, Ola Fosheim Grøstad wrote:
> On Wednesday, 13 January 2021 at 08:33:58 UTC, Guillaume Piolat wrote:
>> [...]
>
> Yes, that is what I suggested also. I implemented it yesterday for deque, stack, etc, all using the same memory layout for 64 bit values (pointers, ints, doubles etc).
>
> What makes it cool is that you can construct datastructures that use the same ranges code, despite having different types.
>
> However, it cannot be done without very negative effects on precise collection, I think. So it is a bad fit for D, since it is GC based, and precise collection is the future...
>
> So, trashed it. (I translated it to C++ instead... :-P)

If it is instantiated T = void* then it would only be safe to use with pointers anyway, thus using T = object would be an option.
January 15, 2021
On Friday, 15 January 2021 at 17:25:59 UTC, Paulo Pinto wrote:
> If it is instantiated T = void* then it would only be safe to use with pointers anyway, thus using T = object would be an option.

That's a minor detail though.

Overall, as long at D is based on GC I guess type erasure should be done by the compiler for traceable pointers. Maybe I'll try again for pointer-free libraries. Basically treating sequences of bytes as values.

For instance,  you don't really need to compare keys as double, int or chars. You might as well compare them as 4 byte, 8 byte etc sequences.

So for a B+tree you could use the same implementation for all kinds of key types.

January 15, 2021
On Friday, 15 January 2021 at 17:36:40 UTC, Ola Fosheim Grøstad wrote:
> On Friday, 15 January 2021 at 17:25:59 UTC, Paulo Pinto wrote:
>> If it is instantiated T = void* then it would only be safe to use with pointers anyway, thus using T = object would be an option.
>
> That's a minor detail though.
>
> Overall, as long at D is based on GC I guess type erasure should be done by the compiler for traceable pointers. Maybe I'll try again for pointer-free libraries. Basically treating sequences of bytes as values.
>
> For instance,  you don't really need to compare keys as double, int or chars. You might as well compare them as 4 byte, 8 byte etc sequences.
>
> So for a B+tree you could use the same implementation for all kinds of key types.

Type erasure can be tricky, even when it is restricted to basic value types of the same size.  This shows up when implementing radix sort where one solution is to map to/from whole numbers (NaN semantics being ignored).

That said, good luck on your explorations.  Meta programming bloat is vulnerable and deserves to be taken down a peg or three.