1. 14
    1. 5

      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.

      1. 4

        Union types also do not form a category-theoretical coproduct, contrary to sum types.

        1. 1

          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.)

          1. 1

            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:

            type Fruit = 'apple' | 'banana';
            type Sphere = 'apple' | 'ball';
            
            const acceptsFruit = (fruit: Fruit) => {
              console.log(fruit);
            }
            
            const acceptsSphere = (thing: Sphere) => {
              console.log(thing);
            }
            
            const toUpper = (s: string) => s.toUpperCase();
            
            let x: string = 'apple';
            toUpper(x); // passes
            acceptsFruit(x); // type error
            if (x === 'apple') {
              acceptsFruit(x); // passes
              acceptsSphere(x); // passes
            }