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
          }