pub fn fuse_loops_2(expr: &mut Expr) {
    if expr.uniquify().is_err() {
        return;
    }
    let mut gen = SymbolGenerator::from_expression(expr);
    expr.transform(&mut |ref mut expr| {
        if let For {
            ref iters,
            ref builder,
            ref func,
        } = expr.kind
        {
            if let Lambda {
                ref params,
                ref body,
            } = func.kind
            {
                // Check whether at least one iterator is over a map pattern, i.e., Res(For(new Appender)) with
                // a simple merge operation.
                if !iters
                    .iter()
                    .any(|ref iter| MapIter::extract(iter).is_some())
                {
                    return None;
                }

                // Now that we know we have at least one nested map, create a new expression to replace the old
                // one with. We start by figuring out our new list of iterators, then build a new lambda function
                // that will call the old one but substitute some of the variables in it.
                let mut new_iters: Vec<Iter> = Vec::new();
                let mut new_elem_exprs: Vec<Expr> = Vec::new();
                let mut let_statements: Vec<(Symbol, Expr)> = Vec::new();
                let mut new_body = body.as_ref().clone();

                let elem_types: Vec<Type>;
                if let Struct(ref types) = params[2].ty {
                    elem_types = types.clone();
                } else {
                    elem_types = vec![params[2].ty.clone()];
                }
                let mut new_elem_types: Vec<Type> = Vec::new();
                let mut new_elem_symbols: Vec<Symbol> = Vec::new();

                for i in 0..iters.len() {
                    // Check whether this iter follows a map pattern, and if so, use that map's inner iter.
                    if let Some(ref map) = MapIter::extract(&iters[i]) {
                        // This was indeed a map pattern; we'll update it to apply the map function directly on the
                        // element values we pull from those iterators in the upper-level loop.
                        let mut value = map.merge_value.clone();
                        let index_ident =
                            Expr::new_ident(params[1].name.clone(), params[1].ty.clone()).unwrap();
                        value.substitute(&map.merge_params[1].name, &index_ident);

                        // For each iterator in the original MapIter, figure out a local variable that will hold
                        // the corresponding item.
                        let mut map_elem_types: Vec<Type> = Vec::new();
                        let mut map_elem_symbols: Vec<Symbol> = Vec::new();
                        let mut map_elem_exprs: Vec<Expr> = Vec::new();
                        for map_iter in map.iters.iter() {
                            // Check whether this iterator is already in our new_iters list; if so, reuse the old one
                            let iter_num;
                            if let Some(pos) = new_iters
                                .iter()
                                .position(|x| iters_match_ignoring_symbols(x, map_iter).unwrap())
                            {
                                iter_num = pos
                            } else {
                                // If it is indeed a new iterator, remember its element type and assign it a symbol.
                                new_iters.push((*map_iter).clone());
                                let elem_type = match map_iter.data.ty {
                                    Vector(ref ty) => ty,
                                    _ => panic!("Iterator was not over a vector"),
                                };
                                new_elem_types.push(elem_type.as_ref().clone());
                                let new_elem_symbol = gen.new_symbol("tmp");
                                new_elem_symbols.push(new_elem_symbol);
                                iter_num = new_iters.len() - 1;
                            }
                            let elem_ident = Expr::new_ident(
                                new_elem_symbols[iter_num].clone(),
                                new_elem_types[iter_num].clone(),
                            )
                            .unwrap();
                            map_elem_types.push(new_elem_types[iter_num].clone());
                            map_elem_symbols.push(new_elem_symbols[iter_num].clone());
                            map_elem_exprs.push(elem_ident);
                        }

                        // If needed, add a Let statement to package the map_elems into a struct, and substitute
                        // that into our value expression; otherwise just substitute the single symbol we're using
                        if map_elem_exprs.len() > 1 {
                            let struct_symbol = gen.new_symbol("tmp");
                            let make_struct = Expr::new_make_struct(map_elem_exprs).unwrap();
                            let struct_ident =
                                Expr::new_ident(struct_symbol.clone(), make_struct.ty.clone())
                                    .unwrap();
                            let_statements.push((struct_symbol, make_struct));
                            value.substitute(&map.merge_params[2].name, &struct_ident);
                        } else {
                            value.substitute(&map.merge_params[2].name, &map_elem_exprs[0]);
                        }

                        // Push an expression for this element
                        new_elem_exprs.push(value);
                    } else {
                        // Check whether this iterator is already in our new_iters list; if so, reuse the old one
                        let iter_num;
                        if let Some(pos) = new_iters
                            .iter()
                            .position(|x| iters_match_ignoring_symbols(x, &iters[i]).unwrap())
                        {
                            iter_num = pos
                        } else {
                            // If it is indeed a new iterator, remember its element type and assign it a symbol.
                            new_iters.push(iters[i].clone());
                            new_elem_types.push(elem_types[i].clone());
                            let new_elem_symbol = gen.new_symbol("tmp");
                            new_elem_symbols.push(new_elem_symbol);
                            iter_num = new_iters.len() - 1;
                        }
                        // Push an expression for this element.
                        let elem_ident = Expr::new_ident(
                            new_elem_symbols[iter_num].clone(),
                            new_elem_types[iter_num].clone(),
                        )
                        .unwrap();
                        new_elem_exprs.push(elem_ident);
                    }
                }

                let new_param_type = if new_elem_types.len() > 1 {
                    Struct(new_elem_types.clone())
                } else {
                    new_elem_types[0].clone()
                };
                let new_param_name = gen.new_symbol("data");
                let new_param = Parameter {
                    name: new_param_name.clone(),
                    ty: new_param_type.clone(),
                };

                let new_params = vec![params[0].clone(), params[1].clone(), new_param];

                // Add a let statement in front of the body that builds up the argument struct.
                let old_param_expr = if new_elem_exprs.len() > 1 {
                    Expr::new_make_struct(new_elem_exprs).unwrap()
                } else {
                    new_elem_exprs[0].clone()
                };
                new_body = Expr::new_let(params[2].name.clone(), old_param_expr, new_body).unwrap();

                // Add any let statements we created for temporary structs.
                for pair in let_statements.iter().rev() {
                    new_body = Expr::new_let(pair.0.clone(), pair.1.clone(), new_body).unwrap()
                }

                // Add let statements in front of the body that set the new_elem_symbols to new_elem_exprs.
                let new_param_ident = Expr::new_ident(new_param_name, new_param_type).unwrap();
                if new_elem_types.len() > 1 {
                    for i in (0..new_elem_types.len()).rev() {
                        new_body = Expr::new_let(
                            new_elem_symbols[i].clone(),
                            Expr::new_get_field(new_param_ident.clone(), i as u32).unwrap(),
                            new_body,
                        )
                        .unwrap()
                    }
                } else {
                    new_body = Expr::new_let(new_elem_symbols[0].clone(), new_param_ident, new_body)
                        .unwrap()
                }

                let new_func = Expr::new_lambda(new_params, new_body).unwrap();
                let mut result =
                    Expr::new_for(new_iters, builder.as_ref().clone(), new_func).unwrap();
                result.annotations = expr.annotations.clone();
                return Some(result);
            }
        }

        None
    })
}
Gets rid of `result(for(d, appender, _))` expressions within the iterators in a loop, replacing them
with direct iteration over the data (e.g. `d` here).

Caveats:
- Like all Zip-based transforms, this function currently assumes that the output of each
expression in the Zip is the same length.
weld/src/optimizer/transforms/loop_fusion_2.rs