Convert ProjectOp and UnwindOp to map/reduce ops.

This is done via the new `WorkflowOp.finalize` function that performs
potentially-irreversible changes to the workflow prior to converting to
`WorkflowTask`. (Fixes #348)

A lot of other changes were made to support this:

* everywhere we use `Js.Expr` is now parameterized so we can specify the
  base of the expression, rather than abusing `this` all the time;
* `ExprOp`s can now _sometimes_ be converted to JS (fixing part of #272);
* map/reduce ops no longer change the WorkflowBuilder `base` to
  `ExprVar` (fixes #326);
* `FoldLeftOp` now handles patching each of its sources when necessary,
  rather than expecting the creator to do it (projecting into `value`
  for the first one, and potentially adding a ReduceOp on the others);
* `WorkflowOp.crush` tracks the `base` so it can patch operations that
  follow a `MapReduceTask` (corollary to the fix to #326), which also
  gives us some info that is useful for #287; and
* `coalesce` now simplifies some `FlatMapOp`s, eliminating ones with a
  single source and flattening nested ones when possible.

`finalize` is also a step toward #227, as that largely implies building
a `Stream` that uses different combinations of `finalize`’s
optimizations.

The one bit of nastiness (there’s always one, isn’t there?) is that I
created a `private FoldLeftOp0` that is generated by `finalize` to
indicate that the `FoldLeftOp` has already been processed. Ideally this
could be noticed in `finalize`, but my attempts were not fruitful.
If MatchOp is part of this party, I'm more on board; I guess that's happening in crush. It's probably nearly always better to skip a pipeline step (and temp collection), where possible. I still think as soon as there's a pipeline, it's probably better to do as many steps as possible there, but that's an empirical question we can answer later on.

On a readability note, I'd like to see these transformations expressed in a more digestable way. Note:
- duplication of `finalize(chain(finalize(src), ...`
- all the recursive calls to `finalize` in general
- `op.map(finalize(_))` isn't really `map`, is it?

Does fixplating workflowop make this any better?