Generate methods for flattening the results of many joins

Here's what the code-gen based solution to #1096 looks like (I'm fine with using a macro instead, but I just wanted to see how hard this would be and it was pretty straightforward).

works for both the result of left/right joins:
```
scala> ((("hi", 12.8), Seq(1,2,3)), 17).flatten
res1: (String, Double, Seq[Int], Int) = (hi,12.8,List(1, 2, 3),17)
```

And for the result of outer joins:
```
scala> type crazy = (Option[(Option[(Option[(Option[Int], Option[String])], Option[Long])], Option[Double])], Option[Seq[Int]])
defined type alias crazy

scala> val x: crazy = (Some(Some(Some(Some(7), Some("hello")), Some(1000L)), Some(12.3)), Some(Seq(1,2,3)))
x: (Option[(Option[(Option[(Option[Int], Option[String])], Option[Long])], Option[Double])], Option[Seq[Int]]) = (Some((Some((Some((Some(7),Some(hello))),Some(1000))),Some(12.3))),Some(List(1, 2, 3)))

scala> x.flatten
res0: (Option[Int], Option[String], Option[Long], Option[Double], Option[Seq[Int]]) = (Some(7),Some(hello),Some(1000),Some(12.3),Some(List(1, 2, 3)))

scala> val y: crazy = (None, Some(Seq(1,2,3)))
y: (Option[(Option[(Option[(Option[Int], Option[String])], Option[Long])], Option[Double])], Option[Seq[Int]]) = (None,Some(List(1, 2, 3)))

scala> y.flatten
res1: (Option[Int], Option[String], Option[Long], Option[Double], Option[Seq[Int]]) = (None,None,None,None,Some(List(1, 2, 3)))

scala> val z: crazy = (Some(None, Some(12.3)), Some(Seq(1,2,3)))
z: (Option[(Option[(Option[(Option[Int], Option[String])], Option[Long])], Option[Double])], Option[Seq[Int]]) = (Some((None,Some(12.3))),Some(List(1, 2, 3)))

scala> z.flatten
res2: (Option[Int], Option[String], Option[Long], Option[Double], Option[Seq[Int]]) = (None,None,None,Some(12.3),Some(List(1, 2, 3)))
```

Doesn't have any tests yet, and the generator is a bit long.
wait, so are we going down this road?

I thought we didn't want to pollute the docs with tons of generated almost identical methods.

Here is another idea: add a .multi method to CoGroupable that returns a MultiCoGroupable which just wraps the current CoGroupable but has tons of methods.

So you would do:

```scala
pipe.multi.join(a, b, c, d)
```
lot quite as clean, but it hides the giant generated methods behind one gate.