I disagree. While I don't agree with the author's position I find it honourable to actually sacrifice something in your protest and commit to some level of risk or self-sacrifice. While its all very nice to gather your friends and stand around with placards for a day, often you're barely risking or sacrificing anything. A cynical assessment would be: "you're just hanging out".
The author isn't hanging out and specifically introducing consequences to those they wish to punish for actions they don't agree with. If more people protested like this we'd see more social change. But people don't like to risk or sacrifice; so we don't. People who reject ethical positions often do not face social consequences.
Consider a world where owning an SUV carried a significant risk that it would be vandalised. People would buy them less and there would be less co2 in the atmosphere due to those willing to sacrifice themselves by spending time in a jail cell for their acts of vandalism.
“Consider a world where you’d be mocked and shamed publicly for having an abortion. People would have them less and there would be less dead fetuses in the world due to those willing to sacrifice themselves by spending time in a jail cell for their acts of shaming.”
Just wanted to make sure you knew how that sounded, since either political side could try to justify their bad behavior.
Consider a world where a pedophilic cabal of billionaires openly announce that they want to obsolete you. You can voice your complaint in this comment box if you disagree with that hypothetical.
That gives that person the opportunity to go out there in our shared spaces and it gives me the opportunity to disagree with them, share my perspective and oppose them. Maybe someone goes to jail or whatever. But conflict is an important part of society.
Rather that than people living in their own bubbles, thinking everyone agrees with them while sitting on their hands and whining into the void and thinking that counts as progress. Put yourself out there, take a risk, engage with your opposition, you might learn something about them or about yourself.
Conflict is fine and should be tolerated. Breaking someone’s car because you’re part of some environmental doomsday cult or publicly identifying an abortion recipient is not.
I was in a fraternity and some city kids came down our street and busted into a few cars. A few of our brothers were up, woke the house and chased one of the kids down. He ended up in the hospital. People arent going to just call the police. You’re thinking you are nelson Mandela in jail and it’s not going to end up that way.
Have you considered what day to day life in such a world would be like? You have your happy path down, sure. Do you not feel like you're missing something?
making a real sacrifice is something that only affects you and the bad guys. fire bomb a data center and go to jail. leak internal chats or code showing your company lied to users and get fired. when third parties get hurt that makes you lord farquaad. "some of you may die but thats a sacrifice im willing to make"
People are free to do exactly that. The problem is very few people actually believe as strongly as they'd like to posture online.
SUVs aren't vandalized because people talk a big game online where there are zero consequences, and shrink down to the level of their actual beliefs in real life.
It's all just posturing to show that you fit in with some crowd. Very few people actually care as much as they want everyone to believe. So, uh, yeah... I guess points to the jqwik crew for being "true believers". Hope that works out as well as it should for them.
cos people will do things for money. Regulate money, tax better, redistribute better. Give more people the power to say "no" as opposed to "holy fuck I need to make rent next month".
Politically addressing needs has the same issue as regulating money, its unpopular either because of billionaire marketing or general ignorance and cognitive dissonance. Also resource allocation is hard when people interpret any level of cut as murder. So you're hemmed in on both sides while FPTP makes it impossible to be honest with the electorate where jetpacks for everyone and free head is what wins you elections, regardless of its delivery.
While some commenters might suggest socialism is the panacea, I think that's just a different format of the same sort of failure. The fundamental flaw in our societies is ourselves, as we build societies that reflect our own failures. We care for ourselves considerably more than we do others, sometimes aggressively against others, sometimes will utter, wilful ignorance of others. The big picture is too hard for our brains to deal with. We have no baseline emotional regulation, humans can wrap themselves into the same emotional state about leaving Britney alone as they do about the death of a loved one. This means everyone's needs seem the same, which makes resource allocation hard.
We see a similar whine about immigration where the abstract is simply: You get $10k and an immigrant moves next door, or you get $0k and an arbitrary person who isn't an immigrant moves in next door. Solve for the status quo. But people will elect governments on a policy of cruelty to think that status quo won't immediately rubber band back.
Not sure I entirely appreciate the use-case vs classical targeting. I'd imagine you're going so fast that you don't really have the opportunity to engage in thought that is particularly useful.
This is a classic debate in programming, literally:
2001: "Beating the Averages" (Paul Graham) [1]
2006: "Can Your Programming Language Do This?" (Joel Spolsky) [2]
Both of these articles argue for the thesis that programmers that have been deprived of certain language features often argue that they don't need those features since they are already comfortable working around the lack of said features.
It's a fancy way of arguing: you don't know what you're missing because you've never had it. Or, don't knock it until you try it.
Consider, is your argument a) I've never used it and don't see a need for it, or b) I've used it before and didn't get any benefit?
I can already do functional programming like map/reduce in C# tho. Not sure what the LISP argument is. Spolsky was saying there's a perf benefit in there somewhere but I'm not seeing how unions give me that.
1. Argue from ignorance. Never try unions in any other programming languages and completely disallow their use in C# codebases that you participate in.
2. Try them out and adopt an informed opinion.
You may even choose to remain in ignorance until someone wastes their own time trying to convince you. But it isn't my job or desire to teach someone who won't put in the effort to learn for themselves.
its not your job to comment spitty replies either but yet you volunteer that time, when you could have been productive instead of whatever the fuck this shit is.
My primary concern with this pattern versus exceptions is calling code can simply discard the resulting problem.
I mean, they have to explicitly unpack the error and then choose to do nothing with it. It requires roughly the same amount of code to do the same with discarding an exception.
Except with a Result type the fact that an exception can occur and should be handled in the first place is explicit.
The problem if anything is that you MUST say something about the error case, despite the common scenario being “pass it forward” — the same reason exception do this by default. Which is also why rust for example special cases Result with the ? operator to do exactly that
Unions are simpler than subclasses and more powerful than enums, so the use cases are plentiful. This should reduce the proliferation of verbose class hierarchies in C#. Algebraic data types (i.e. records and unions) can usually express domain models much more succinctly than traditional OO.
Really not. You can, of course, having instead a delegate to evaluate the expression. But then that's all you can do. You can't pretty-print it, for example, or optimize it, or whatever.
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.
> 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.
"Non-discriminated" unions (i.e. untagged unions) are even less supported. TypeScript seems to be the only really popular language that has 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?
I think "what problems do they solve that I can't already solve" is the wrong way to look at it. After all, ultimately most language features are just syntactic sugar - you could implement for loops with goto, but it would be a lot less pleasant. I think that unions aren't strictly necessary, but they are a very pleasant to use way of differentiating between different, but related, types of value.
Ok. I'm just trying to understand what code I'm replacing with them. Like I wanna see the before and after in order to gain the same level of excitment as other people seem to have for them.
Often the explanations just seem rather abstract which makes it harder to appreciate the win, versus the hideous sort of code that might appear when they're misused.
while visiting https://dotnetfiddle.net and typing the code samples in, experimenting with what manner of changes and additions to the code cause the compilation to fail, and considering how you would leverage those abilities in your everyday development work.
I think this would be even more powerful if you then come back and re-read some of the pro-Union comments in this very thread.
The value is realized when you have both discriminated union types _and_ language pattern matching (not regex). Then it's not just a way to structure data but a way to think about how to process it.
I think it's almost always about making code more concise and programming more ergonomic. Assembly could already solve all the problems higher-level languages can solve. Yet we didn't discard them as useless.
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.
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.
Simple example that I use often when writing API clients:
In current C# I usually do something like
public class ApiResponse<T>
{
public T? Response { get; set; }
public bool IsSuccessful { get; set; }
public ErrorResponse Error { get; set; }
}
This means I have to check that IsSuccessful is true (and/or that Response is not null). But more importantly, it means my imbecile coworkers who never read my documentation need to do so as well otherwise they're going to have a null reference exception in prod because they never actually test their garbage before pushing it to prod. And I get pulled into a 4 hour meeting to debug and solve the issue as a result.
With union types, I can return a union of the types T and ErrorResponse and save myself massive headaches.
I think I get it but I'm not really sure what I'm gaining over exception types. With an intelligent use of exceptions I can easily specify the happy path and all the error paths separately which seems really nice to me, because usually the behaviour between those two outcomes is rather different.
> I think I get it but I'm not really sure what I'm gaining over exception types. With an intelligent use of exceptions I can easily specify the happy path and all the error paths separately which seems really nice to me, ...
Until your coworker comes along and accidentally refactors the code to skip the exception catching and it suddenly blows up prod.
With tagged unions you can't accidentally dereference to the underlying value without checking if it's actually proper data first.
> Until your coworker comes along and accidentally refactors the code to skip the exception catching and it suddenly blows up prod.
can't my co-worker just use this pattern and discard an error result the same? I'd argue its easier as the stack wont unwind by default because the error is returned instead of thrown.
Not quite, as the compiler wouldnt let let you use the "value" in that case. So if you discard the error path, you simply wont have any value to use further on in the code.
Exceptions are significantly slower than normal control flow in C# (about 10,000 times slower). It's also pretty non-idiomatic in both C# and most other languages I've worked in to use exceptions instead of a switch statement or similar to handle an HTTP error code. Also there can be multiple possible non-error responses from an endpoint you need to differentiate between, and exceptions would make zero sense in that case.
Union/sum types are generally a good thing. Even Java added them. They tend to be worth “the madness”. Now the rest of all the crazy C# features might be a different question.
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?
Given that they already made the billion dollar mistake, I find their handling for nulls the best possible thing they could do at this point. I’d hardly call it crazy — rather, it’s exceedingly pragmatic.
A common use case for the sum type is to define a Result (or Either) type. Now, C# not having checked exceptions is not as much in need for one as Java is, but I could still
imagine it being useful for stream
like constructs.
oo and support for exceptions, in particular checked exceptions, was a mistake of the 90s. We know better today, there’s a reason for why modern languages like go/rust/swift don’t use them, and why many use c++ with exceptions disabled.
Checked exceptions are great. There is no difference between them and results/unions except syntax. The below all express the same thing. Swift in fact uses the checked throws syntax.
Result<T,E> fn()
T | E fn()
T fn() throws E
fn() throws(E) -> T
The problem with C# is that it’s so overloaded with features.
If you come from one codebase to another codebase by a different team it’s close to learning a completely new language, but worse, there is no documentation I can find that will teach me only about that language.
Throw in all the versioning issues and the fact that .Net shops aren’t great about updating to the latest versions, especially because versions, although technologically separated from Visual Studio, are still culturally tied to it, and trying to break that coupling causes all kinds of weird challenges to solve.
Then stuff like extensions means your private codebase or a 3rd party lib may have added native looking functionality that’s not part of the language but looks like it is.
Finally, keywords and operators are terribly overloaded in C# at this point, where a keyword can have completely different meanings based on what it’s surrounded by.
LLMs are a huge help here, since you can point to a line of code and ask them to figure it out, but it still makes the process of navigating a C# codebase extremely challenging.
So I can see why someone may be unhappy to see yet another feature. It’s not just this one feature. It’s the 100s of other features that are hard to even identify.
I am all for minimalism but "If you come from one codebase to another codebase by a different team it’s close to learning a completely new language" I really don't agree. It's not that big. Just sounds like a skill issue
I switched between dozens of similar codebases over a period of 3-4 years (pre AI) when I was consulting and did multiple projects in multiple languages (well, only 1 in rust).
In my experience switching between the C# projects was always the worst. The codebase semantics diverged in ways I simply didn’t see in the Java/C++ codebases.
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.
A function that returns `Result<T,E>` is not a `T` and cannot be implicitly converted to one. If you want to use that `T`, the only way is writing code that drops or handles the `E`. If you don't, your program does not compile.
Compare this to exceptions, where the type is just `T` and can be used without further ceremony. You can discard the error by forgetting a handler. Now you have a program that occasionally crashes.
Follow-up: Are there async exceptions? A `Result` is just data that can be awaited. How would that work with exceptions?
reply