> This has the benefit of giving you the ability to refer to a case as its own type.
A case of a sum-type is an expression (of the variety so-called a type constructor), of course it has a type.
datatype shape =
Circle of real
| Rectangle of real * real
| Point
Circle : real -> shape
Rectangle : real * real -> shape
Point : () -> shape
A case itself isn't a type, though it has a type. Thanks to pattern matching, you're already unwrapping the parameter to the type-constructor when handling the case of a sum-type. It's all about declaration locality. (real * real) doesn't depend on the existence of shape.
The moment you start ripping cases as distinct types out of the sum-type, you create the ability to side-step exhaustiveness and sum-types become useless in making invalid program states unrepresentable. They're also no longer sum-types. If you have a sum-type of nominally distinct types, the sum-type is contingent on the existence of those types. In a class hierarchy, this relationship is bizarrely reversed and there are knock-on effects to that.
> You declare the sum type once, and use it many times.
And you typically write many sum-types. They're disposable. And more to the point, you also have to read the code you write. The cost of verbosity here is underestimated.
> Slightly more verbose sum type declaration is worth it when it makes using the cases cleaner.
C#/Java don't actually have sum-types. It's an incompatible formalism with their type systems.
Anyways, let's look at these examples:
C#:
public abstract record Shape;
public sealed record Circle(double Radius) : Shape;
public sealed record Rectangle(double Width, double Height) : Shape;
public sealed record Point() : Shape;
double Area(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
Point => 0.0,
_ => throw new ArgumentException("Unknown shape", nameof(shape))
};
ML:
datatype shape =
Circle of real
| Rectangle of real * real
| Point
val result =
case shape of
Circle r => Math.pi * r * r
| Rectangle (w, h) => w * h
| Point => 0.0
They're pretty much the same outside of C#'s OOP quirkiness getting in it's own way.