Live data from Hacker News

.NET (OK, C#) finally gets union types

andrewlock.net

111–120 of 300 posts

Re: .NET (OK, C#) finally gets union types

#111
post #93

Earlier quoted context omitted.

What's wrong with Hindley-Milner?

Leaving out types in public API's makes type errors hard to understand. Types should be declared in the API and bidirectional type inference used in the implementation. https://jimmyhmiller.com/easiest-way-to-build-type-checker

Eh. This causes some problems for rust. Right now you can have a function return impl Trait instead of a concrete type. Very handy - and essentially required by async functions.

But the language also requires that types have names in lots of places. For example, you can't store an 'impl Trait' in a struct. You can't make a type alias of an impl Trait. And so on. As a result, async rust can only interact with a butchered subset of the language. (You can work around this with Box> but performance suffers.)

There's a proposal[1] to fix this. But the proposal has been under discussion for (checks watch) 7 years now. Until this lands, async remains a second class citizen in rust.

This entire problem stems from rust's early decision to requiring concrete types at interface boundaries.

https://github.com/rust-lang/rust/issues/63063

Re: .NET (OK, C#) finally gets union types

#112
post #97

As a big user and fan of c# but this is a miss, as it always boxes value types.

When I cared about C# (which is no longer the case), I was lightly involved in the discussion for this and sibling features - mostly theorycrafting exactly your ask: the JIT team very succinctly expressed extreme disinterest in adding support of any kind. The C# compiler could do it to a degree, but there would be too many caveats to make it actually useful. Unless the JIT team has a change of heart, you're probably…

What do you use these days language-wise?

Re: .NET (OK, C#) finally gets union types

#113
post #30
post #8

I used to see some excitement around .net core several years ago. I haven’t heard or seen much in the wild. Is anyone using .net on systems other than windows nowadays?

Yes; many (Alpine/Debian) containers in K8s on GKE for production rail ticketing infra in the UK. There's not tons of noise being made because for the most part it all, Just Works and that's fairly boring. Perf, memory usage etc gets better every release. As an ecosystem, I'm pretty happy with it. I reach for other languages for smaller microservices.

What's preventing you from using C# for smaller microservices? And what do you reach for?

Re: .NET (OK, C#) finally gets union types

#114

Earlier quoted context omitted.

I daylight as a .NET dev professionally. I completely agree with what you have wrote, but I do not think C# is particularly unique in that regard. I would say many common compiled languages are on the same path, e.g., Swift, Java, Kotlin, etc.. As time progresses, I am finding it harder to justify using C# for a greenfield project.

> As time progresses, I am finding it harder to justify using C# for a greenfield project. Are you able to elaborate why? Just curious.

Not OP here - but for me it’s the open source ecosystem. Java just wins in terms of scope, scale, and stability.

I love C# the language, but the ecosystem is a ghetto.

Re: .NET (OK, C#) finally gets union types

#115
post #44

Earlier quoted context omitted.

All the weird cruft around nullability, for starters. Once again confirming that allowing null references is usually a mistake.

Do you mean the implicit nullable types? Now that you can make nullable explicit instead I really don’t have much issues with it. It is part of the type system, as it should, and you have null coalescing operators. Is it still problematic or are you dealing with older codebases where you cannot set the nullable pragma?

Yes, all that stuff. I try to stick to F# where no special syntax is required for missing values (via Option).

Re: .NET (OK, C#) finally gets union types

#116
post #88

Most people don't know that there are fundamentally two different kinds of "union" types: "tagged unions" and "untagged unions". Now .NET is introducing tagged unions, but unfortunately they stick to the popular tradition of calling them just "unions", which greatly adds to the confusion. To clarify, these tagged unions are fundamentally different from the untagged unions that can be found in languages like Typescrip…

Underlyingly, all unions are the same concept, exposed by C's `union` keyword, namely overlapping memory (as opposed to `struct`s, which are sequential memory). You can add a discriminator tag to your unions, your call. If it is impossible for there to be ambiguity (e.g. you track the state somewhere else, and you're dealing with a large array), it may be redundant / memory inefficient to tag every item.

Having these tags be a language construct is just a DX feature on top of unions. A very handy one, but it doesn't make tagged and untagged unions spring from different theoretical universes. I enjoy the ADT / set-theoretic debate as much as the next PL nerd, but theory ought to conform to reality, not vice versa.

Re: .NET (OK, C#) finally gets union types

#117

Earlier quoted context omitted.

> As time progresses, I am finding it harder to justify using C# for a greenfield project. Are you able to elaborate why? Just curious.

Not OP here - but for me it’s the open source ecosystem. Java just wins in terms of scope, scale, and stability. I love C# the language, but the ecosystem is a ghetto.

I see this reason a lot but what are some actual examples of what is lacking in the .NET ecosystem vs. Java?

Re: .NET (OK, C#) finally gets union types

#118

Earlier quoted context omitted.

An enterprise shop I co-op'd at was porting one of their apps from Xamarin to MAUI when I worked there, but certainly it doesn't have much mindshare (if any) amongst SE undergrads at my university.

Someone I know who works with .net says that there is still no replacement for full Visual Studio for development, which is Windows only.

I used to think this. Hopped to rider 4 years ago and haven't missed it except for .sqlproj development.

Re: .NET (OK, C#) finally gets union types

#119

Earlier quoted context omitted.

none of that applies to my position. I have an appreciation for almost all of C# and am comfortable in the framework. I just want to know what situations would be better suited to using them than traditional approaches. I get there's an .Either pattern when chaining function calls so you don't have to do weird typing to return errors, but I'm using exceptions for that anyway, so the return type isn't an issue.

The Result pattern can be a lot more ergonomic than exceptions. Microsoft C# guidelines recommend try-parse (which is just the Result pattern, albeit somewhat cludgy with no unions) over exceptions. https://learn.microsoft.com/en-us/dotnet/standard/design-gui...

the result pattern doesn't force you to handle the exception though. You can just discard the result.

Re: .NET (OK, C#) finally gets union types

#120
post #23

Earlier quoted context omitted.

Discriminated union types are a really fundamental building block of a type system. It's a sad state of matters that many mainstream languages don't have them.

ok, so what problems do they help me solve that I can't already solve? Is it just that we can make code more concise or am I missing a trick somewhere?

Object-oriented polymorphism (interfaces, inheritance) is for when you have a fixed set of methods to implement but an unbounded set of types that may want to implement them.

As a consumer, you cannot change the methods, but you can add a subtype. When you subtype an abstract class or an interface, the compiler does not let you proceed until you have implemented all the methods.

Discriminated unions are for the exact opposite situation, when you have a fixed set of subtypes, but unbounded set of methods to implement on them. As a consumer, you cannot add a subtype, but you can add a new method. When you write a new method, the compiler does not let you proceed until you have handled all the subtypes.

Good languages should support both!

The best example is abstract syntax trees, the data types that represent expressions and statements in a programming language. "Expression" breaks down into cases: integer literal, string literal, variable name, binary operations like add(expr1,expr2), unary operations like negate(expr), function call(functionName, exprs), etc.

Clearly all of these expression subtypes should belong to a base type `Expression`. But what methods do you put on `Expression`? If you're writing a compiler, you have to walk this syntax tree many times for very different purposes. First you might do a pass on it where you "de-sugar" syntax, then another pass where you type-check it and resolve names in the code, then another pass where you generate assembly code from it. Perhaps your compiler even supports different backends so you have a code-gen path for x86, another for ARM, etc. You'll likely want a pretty-printer so you can do automatic reformatting, maybe you want linting support, etc.

If you look at all those concerns and say that each subtype of `Expression` must implement methods for each one, then you end up with untenable code organization. Every expression subtype now has a huge stack of methods to implement all in one file, dealing with stuff from totally different layers of the compiler. It's a mess.

It's much cleaner to have the "shape" of the expression defined in one place without all that clutter, and then in each of those areas of the code you can write methods that consume expressions however they need, so each of those separate concerns lives in its own silo.

------------------------------------------------

Some real code (but it's F# not C#) to look at.

AST for my SQL dialect: https://github.com/fsprojects/Rezoom.SQL/blob/master/src/Rez... Typechecker code: https://github.com/fsprojects/Rezoom.SQL/blob/master/src/Rez... Backend code that outputs MS TSQL from it: https://github.com/fsprojects/Rezoom.SQL/blob/master/src/Rez...

------------------------------------------------

If you're an old hand at OO you may be familiar with its actual answer to this problem, the "Visitor" pattern. See System.Linq.Expressions.ExpressionVisitor. However, once you've used a language with good union and pattern matching support, this feels like a clunky hack. Basically the mirror image of a language without real object orientation imitating it by passing around closures and structs-of-closures.

------------------------------------------------

It doesn't just have to be compiler stuff. A business app data model can use this too. Instead of having:

    public class DbUser
    {
        public EmailAddress Email { get; set; }
        public PasswordHash? Password { get; set; } // null if they use SSO
        public SamlEntityProviderId? SamlProvider { get; set; } // null if they use password auth
    }

You could have:

    type UserAuth =
        | PasswordAuth of PasswordHash
        | SSOAuth of SamlIdentityProviderId
The implementation details of those different auth methods, the UI for them, etc. don't have to be part of the data model. We do have to model what "shapes" of data are acceptable, but "doing stuff" based on those shapes is another layer's problem.
Post reply on HN