|<<>>|4 of 304 Show listMobile Mode

Mads Torgerson on union types, existential types, and C# missteps

Published by marco on

This is a nearly 100-minute-long interview and discussion about programming-language design and evolution. It gets deep into the weeds on very specific and relatively advanced language features. While a feature may eventually feel quite simple to use, the considerations about how to design it and how to fit it into the landscape of the rest of the language can be very, very complex. There are a lot of moving parts to consider in a language, runtime, community, and ecosystem as established as .NET and C#.

The Exciting Future of C# with Mads Torgersen by Nick Chapsas (YouTube)

Existential Types

At about 45:00, Mads describes a higher-level typing system that allows for “hiding” some generic-type parameters.

“There are various features that are related but not quite the same. There are ‘associated types’ in some languages and there’s what one scholar calls ‘abstract types’ [I think he meant ‘existential types’], which might be my favorite version of the feature, which are kind of an alternative to generics or a kind of generics.

“The really short version is they help you not have so damn many type arguments all the time, yeah? Okay; essentially think of it as a class. Instead of a class having a type parameter saying, I’m an animal with a type argument saying which kind of food it eats…that means every time you talk about animals, you have to pass type arguments around. That’s really annoying because what kind of food it eats is inherent to it. It shouldn’t be like something on the outside; it should be a member, saying my food type is [whatever]

“…and if you do that, then you can kind of tamp down on a lot of the…sometimes you just end up in generics overload, or passing the same stuff around. And every one of these related types has a type argument for which particular implementation of the other related interfaces it is using and they all carry the same five type arguments around all the time.

“So that would be a feature, if we can get it right, and if we can work it into the runtime, and it’s limited—and we are occasionally talking about it—that could be a really really beautiful and quite impactful addition to C#.

It is described in detail in Proposal: Existential types for interfaces and abstract types #8711 (GitHub). While Mads’s description of it made my ears perk up, the proposal does a better job of explaining it.

An interface might look like this:

interface ICounter<protected T>
{
    T Start { get; }
    void Next(T current);
    bool Done { get; }
}

Describing the visibility of the generic parameter with the keyword protected is part of the proposal.

Since the parameter is protected, external users would use the interface without the generic parameter, like this:

void M(ICounter ic)
{
    var × = ic.Start;
    while (!ic.Done)
    {
        × = ic.Next(x);
    }
}

Only an implementation is required to provide a type parameter:

class Counter : ICounter<int>
{
     int Start => 0;
     int Next(int current) => current + 1;
     bool Done => current == 42;
}

Type unions

At 58:00, they discuss what something like “discriminated unions” or “tagged unions” would look like in a decidedly object-oriented language like C#.

Mads: We arrived at this degree of clarity around what our options are. The type unions—use of the word ‘types’ there reflects that one conclusion that we reached is that, in C, unions should be ‘unions of types’. If you look at F# or other functional languages, discriminated unions are not unions of types. They’re unions of something with a name, a tag-discriminator, whatever you want to call it, the tagged unions that can then be deconstructed to give you values of one or more types. So the different options are like ‘named options’ but they’re not things in and of their own right. They’re just a means to get to what’s inside.

“In C#, one thing that we agree on is that that has to change. It has to be that the things are types. So you don’t have to pattern on a union and get an A or you get a dog and immediately you have to decompose it into how many legs it has and and what it eats. You know what its name is. You can carry it around as a dog. It makes sense in its own right and it can be its own object. So that’s essentially trying to take an object-oriented view on what discriminated unions would look like in C#.

Nick: Fundamentally, discriminated unions are a very functional-programming-like concept and they don’t fit in a language that already has inheritance right? Because the idea is it’s their version of inheritance.

Mads: Exactly.”

Design mistakes in C#

At 01:22:00, Nick asks what are Mads’s three least-favorite/most-hated features of C#, things that he wishes had never gone in or that, in a better world, he would remove.

  1. Events, because they should never have been a language feature. They should have been a runtime/library feature, at most.
  2. Delegates, because they were forced to be collection types because of events. Their execution dynamics are indeterminate (because of ordering) and they actually break covariance (kind of like arrays).
  3. Void should have been a type (as it is in Typescript and Eiffel). Its being a language feature means that they bifurcate all other support, like requiring a distinction between Func and Action.
  4. dynamic is a pretty great feature academically but the value of the feature ended up being much less than they’d hoped. “Performance-wise, it’s a disaster. There’s a whole bunch of infrastructure to maintain it. It doesn’t carry its own weight.”

Nullability

The final part of the discussion is about nullability-checking in the compiler, and its limitations and strengths. “The point is that it’s so much better than nothing.” The feature makes you think about what even should be allowed to be null.

My advice is to avoid returning or accepting null unless you absolutely can’t avoid it. Don’t ruin your API, of course, but be absolutely sure that null needs to be an option. It is much easier to write code to APIs that never return null. Consider using sentinel objects instead. The feature in C# has gotten a lot better and is very good now. init and required properties helped finalize the feature.