Jump to page: 1 26  
Page
Thread overview
January 11
I don't want to be mistaken. I know what the differences of generics (cf. Java, C#) are and what templates (cf. D, C++) are. Maybe my question has obvious answers and I just don't see them.

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. Templates can be used in some use cases, templates have their specific use cases (like DbI). What seems to never mentioned is that generics have their specific use cases, too.

What I may be missing, is how Java-esce generics can be implemented using D's templates (without much effort). What I'd expect from generics is this:

A generic class or interface states its requirements (base classes, interfaces, [never seen in the wild:] subclasses, ...) to its type parameters exactly. Everything that is part of the implementation is checked when the generic aggregate is defined. It is not necessary to instantiate a rainbow of types to be sure the implementation is valid for every kind of type parameter the programmer intends to support.

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])

Generics are really great for specifying the expectations of your type parameters' requirements and letting the compiler make sure the requirements suffice to allow for the stuff you want to work.
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.

Also, one feature D doesn't have, is expressing the precise union or intersection of interfaces: Say you have two interfaces I and J. 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.

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. Maybe they can be implemented in a library rather easily. I know that me failing to do that doesn't prove it's impossible or even hard.

[1] https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/variance-in-generic-interfaces
[2] https://docs.oracle.com/javase/tutorial/java/generics/lowerBounded.html
[3] https://docs.oracle.com/javase/tutorial/java/generics/upperBounded.html
[4] https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html?ref=hackernoon.com
January 11
On Monday, 11 January 2021 at 18:23:20 UTC, Q. Schroll wrote:
> I don't want to be mistaken. I know what the differences of generics (cf. Java, C#) are and what templates (cf. D, C++) are. Maybe my question has obvious answers and I just don't see them.
>
> The only programming language I know of that has both, templates and generics, is C++/CLI and ones closely related.

I think you are really talking about generics in Java and is interpreting the semantic landscape through that lense?

There are many variations:

https://en.wikipedia.org/wiki/Generic_programming

C++ "concepts" is to a large extent syntactical sugar that makes it easier to specify interfaces... so I don't think it has a significant semantic impact. Mostly a usability impact.
6 days ago
On Monday, 11 January 2021 at 18:23:20 UTC, Q. Schroll wrote:
> [snip]

I recommend you check out Atila's Github profile. I think that he wrote something to address the shortcomings of D template constraints. Also considering your way to think about design, I think there's a good chance you're going to like Tardy and/or Mirror.

Keep in mind though that as Mirror isn't yet officially announced, Atila probably does not consider it quite production-ready yet.
6 days ago
On Monday, 11 January 2021 at 18:23:20 UTC, Q. Schroll wrote:
> I don't want to be mistaken. I know what the differences of generics (cf. Java, C#) are and what templates (cf. D, C++) are. Maybe my question has obvious answers and I just don't see them.
>
> [...]

I agree with everything you said. Even though I greatly enjoy DbI, many times I miss C# generics and TypeScript's powerful, yet fully-erased typing. Type-checking has been described as the most successful form of light-weight formal verification and some of my TypeScript usage feels like this (compared to using pure JavaScript).
Sometimes I don't need **or want** different code to be generated depending on the generic parameters, I just want the type system semantics and the light-weight formal verification (e.g. implementing units of measure).
6 days ago
On Tuesday, 12 January 2021 at 12:25:31 UTC, Dukc wrote:
> On Monday, 11 January 2021 at 18:23:20 UTC, Q. Schroll wrote:
>> [snip]
>
> I recommend you check out Atila's Github profile. I think that he wrote something to address the shortcomings of D template constraints. Also considering your way to think about design, I think there's a good chance you're going to like Tardy and/or Mirror.
>
> Keep in mind though that as Mirror isn't yet officially announced, Atila probably does not consider it quite production-ready yet.

Generics is a pure type system feature and no library can emulate it perfectly. As mentioned in my other post, by using D meta programming to implement things like Tardy, even in the best case we still can end up with different instances of functions, sometimes containing the same machine code, just because of a difference between template arguments. Yes, linkers can sometimes remove identical code, but why pay the frontend + backend compilation time cost?
6 days ago
On Tuesday, 12 January 2021 at 13:25:36 UTC, Petar Kirov [ZombineDev] wrote:
> Generics is a pure type system feature and no library can emulate it perfectly.

The term "generics" just means parametric types. I think you guys have something more specific in mind based on C#/Java?


6 days ago
On Tuesday, 12 January 2021 at 13:36:09 UTC, Ola Fosheim Grøstad wrote:
> I think you guys have something more specific in mind based on C#/Java?

Yeah, in the D context since we obviously have generic templates, I'd take it to mean the Java style.

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.

Lots of potential for use inside druntime itself, changing the existing void* + TypeInfo pairs into templates can mean no more annoying typeinfo requirement... but then it causes runtime bloat. So we probably actually do want to merge, and Java-style generics are a very good way to do it.

We could like, just for example

_d_arrayappend(ref T[] arr, E ele)

forward back to the same implementation functions with pointers, static if branching on special requirements like postblit, just passing the exact parameters it needs to make it work instead of typeinfo... but then having no additional codegen, no additional symbols, no additional indirection, by using the generics.

Doing this in today's D comes close with inlined templates but it is still far more expensive than it has to be and really annoying to try to describe. A sum type definition in the template that actually merges would surely help.
6 days ago
On Tuesday, 12 January 2021 at 14:45:36 UTC, Adam D. Ruppe wrote:
> [snip]
>
> Yeah, in the D context since we obviously have generic templates, I'd take it to mean the Java style.
>
> 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.
> [snip]

Not just classes? Could you explain a bit more about what you are thinking in that paragraph? If it's not a class, then are you talking about a struct? As in something like below. Does the same approach work when it's not a class?

generic Number(T)
    if (isNumber!T)
{
    struct Number {
        T x;
    }
}


6 days ago
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 haven't looked very closely at Phobos, but it seems like it is a bit compile time heavy ("over complicated") compared to what is most useful ("most common usage"). Perhaps simpler library type semantics with less compile time load would help.

Regarding code gen, I thought LDC cached code gen and would avoid emitting the same code twice? If that does not work, then maybe one can bring the IR to a normal form such that more code will be recognized as equivalent?


> 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.

But we probably could fix this by designing libraries with reinterpret casting through (void*) if desired. The template itself does not generate any code.

I imagine we could sketch up an alternative std lib based on the most commonly used semantics that Phobos provides. It should of course work alongside Phobos so that people can transition/choose.

It does not sound like we need Java-style generics, to me, but I could be wrong.

Maybe your own lib could be a starting point for such an effort?


> Lots of potential for use inside druntime itself, changing the existing void* + TypeInfo pairs into templates can mean no more annoying typeinfo requirement... but then it causes runtime bloat.

Ideally the runtime should not contain anything that not every D program uses...

Maybe some kind of auto-import would make it possible to slim down the runtime to basically nothing. Basically some kind of feature that lets certain syntax trigger an import in application code (but not in library code). You could config this for the project in a config file and basically choose e.g. what kind of string interpolation semantics you want for you application.


> Doing this in today's D comes close with inlined templates but it is still far more expensive than it has to be and really annoying to try to describe. A sum type definition in the template that actually merges would surely help.

Not exactly sure what you meant by merging here? But if D is going to continue being GC based then a discriminating union type is needed, so that is an area that requires some rethinking (sum types, unions).

(I think the only thing that prevents fully precise collection is unions with traced pointers? And precise collection is needed for reliable GC based destruction/finalization.)


6 days ago
On Monday, 11 January 2021 at 18:30:19 UTC, Ola Fosheim Grøstad wrote:
> On Monday, 11 January 2021 at 18:23:20 UTC, Q. Schroll wrote:
>> I don't want to be mistaken. I know what the differences of generics (cf. Java, C#) are and what templates (cf. D, C++) are. Maybe my question has obvious answers and I just don't see them.
>>
>> The only programming language I know of that has both, templates and generics, is C++/CLI and ones closely related.
>
> I think you are really talking about generics in Java and is interpreting the semantic landscape through that lense?

Yes. But I'm not saying: Do exactly as Java does. I'm saying: Java has this very interesting concept that D might want to learn, too.

> C++ "concepts" is to a large extent syntactical sugar that makes it easier to specify interfaces... so I don't think it has a significant semantic impact. Mostly a usability impact.

You might have mistaken me. C++ has no generics in the sense I meant. Generics to me means not that List<A> and List<B> result in one List type; really, I care about much more semantics than implementation. C# will create two different List classes if A or B is a value type. C#'s generics are copied like templates; but I digress, because I don't care.

In C#, even if you require a generic type parameter is a value type (which you can and entails a template-like implementation under the hood), the compiler makes sure that everything in your generic type is semantically valid for every type argument satisfying the conditions you state.
For example, if you call T.method(), but no constraint says that T.method() exists, the compiler rejects the code. On the other hand, in C++ or D, if you call T.method() in a template and only test types that happen to have method(), everything is good between friends (Andrei's quote).
I'm not arguing you *always* want static checks, obviously DbI hardly works that way. All I'm saying is, the big thing generics would bring to D is **statically ensuring** that the requirements put on type parameters suffice for the implementation to be valid.
« First   ‹ Prev
1 2 3 4 5 6