## Notes for October 24th, 2022

This meeting distilled the previous use case discussions to a few broad concepts of unions, and we talked about some basic features and tenets that we want to see in unions.

### Concepts

* `Result<TResult, TError>`/`Option<T>`
    * Very interested in syntactic shortcuts here.
    * Monads over the `T` inside
    * Forces users handle errors up front
    * Is short declaration of these types of unions necessary?
        * Maybe not? Maybe if the BCL ships Result/Option itself, people may not actually need to declare many of their own versions of these.
* Internal State with associated data
    * UI States
    * `BoundNode`
    * Subsets of these are often useful: State machines only transitioning from X -> Y, not Z -> Y, or only accepting certain types of expressions.
        * Anonymous definition of these might be useful. `(BoundLiteral | BoundMethodGroup)` inline, for example
    * Simplifies handling of state and state transitions, potentially enforces explicit handling of cases.
* `OneOf<T1, ..., Tn>`
    * Passing heterogeneous data around
    * Anonymous definition might also be useful
        * How would this reconcile with a homogeneous subset?
    * Potentially easier to integrate into existing codebases

### Union versioning and compat

* What's our compat strategy for public APIs with this type of union in them?
* Matters particularly in the same areas as variance: what happens when public apis start returning a new result?
* Or stop accepting a result variant?
    * Maybe this one doesn't matter as much?
* Source compatibility in an return position is not guaranteed when revving a union type
* Binary compatibility in a input position?
    * Historically, we've cared about this
    * If my public function accepts a new case, all existing code should still work
    * On the other hand, if I add a new input to some interface a consumer is implementing, they may get a case they weren't expecting
* In general, we're not worried about maintaining binary compatibility. We just want to make sure that API authors can be intentional about when they
  want to make a breaking change and make it obvious that they did.
* Do we want to _ensure_ that binary breaks happen when users encounter new cases?
    * No. If it falls out then it falls out, similar to required members.

### Opaque unions

* F# has these:
  ```fs
  type C =
      private // or internal
      | Case1 of int
  ```

  These create an interchange type that appears as a union to the file or assembly that created them, but is opaque to consumers. Do we need this?
    * We think this is probably just existing base types/interfaces in C#

### Exhaustiveness

* List all valid subtypes in the base type?
    * Compiler error when adding a new type?
    * This is Java's approach in JEP 409 - Sealed Classes
* For sufficiently large hierarchies, maybe exhaustiveness isn't always as useful as hoped?
    * May often be working on subsets, and forcing handling of all unexpected values might erode desired errors when adding new expected cases.

### Conclusions

* Subsets of larger unions keep coming up in our discussions, though we need to make sure we're not being overly colored by roslyn.
    * Subsets of unions may call for an anonymous union syntax.
    * Having different implementation details for sets of homogeneous data and sets of heterogeneous data would likely be confusing for users. A singular lowering
      for subsets is likely needed.
* Compatibility with existing forms is important. Users should not have to perform large refactorings to take advantage of any improvements we come up with here.
    * Importantly, we're not looking to deprecate existing forms of creating hierarchies.
* Experiencing source breaks when adding or removing new cases to unions is likely both unavoidable and desirable. We're ok with binary breaks as well, but won't
  be attempting to _ensure_ that they occur.
