Use multiple dispatch in to simplify sets

Various functions for the simplification of `Union`s and `Intersections` are defined using multiple dispatch.  

### Simplification Process

`Union`/`Intersection` construction proceeds in the following steps:

1. Construct Union/Intersection with full args
2. Flatten / perform trivial/always wanted simplifications
3. Call `Union/Intersection.reduce` which reduces the arguments in the following two ways
    a.  Performs known global simplifications (`Intersection` that contains `EmptySet` is `EmptySet`)
    b.  Calls multiply dispatched function `union/intersectoin_simp` on pairs of arguments

### Performance

Sets already had this design, they just did double dispatch internally with lots of `if other.is_Foo` checks.  This just makes the typing more explicit.  No tests were altered for this PR.