Having never worked in a language with union types but no sum types, I wonder if I am correct in thinking that you would almost always want to use union types with manual tagging to emulate sum types. Anyone with experience in union-type-only languages want to weigh in?
As a concrete example, I feel like I would almost always prefer
enum UserKind { Id, Name }
type User =
{ kind : UserKind.Id, id : Number }
| { kind : UserKind.Name, name : String }
over
type User = String | Number
(Apologies for the unchecked TypeScript. I did say I’ve never worked in a language with union types.)
I would think that the sum type language version (Haskell for example) would look something more like this:
data User = UserById { id :: Int }
| UserByName { name :: String }
To me, the two are equivalently legible, the sum type might look more elegant with the value constructors. But union types are more flexible, since a value can belong to more than one type:
I was kind of thinking “what’s the point” until I hit “Singleton null versus a null for every type”, and then the difference became much more obvious.
Union types also do not form a category-theoretical coproduct, contrary to sum types.
Having never worked in a language with union types but no sum types, I wonder if I am correct in thinking that you would almost always want to use union types with manual tagging to emulate sum types. Anyone with experience in union-type-only languages want to weigh in?
As a concrete example, I feel like I would almost always prefer
over
(Apologies for the unchecked TypeScript. I did say I’ve never worked in a language with union types.)
I would think that the sum type language version (Haskell for example) would look something more like this:
To me, the two are equivalently legible, the sum type might look more elegant with the value constructors. But union types are more flexible, since a value can belong to more than one type: