///|
fn optimize(filter : Filter) -> Filter {
  match filter {
    Pipe(left, right) => {
      let l = optimize(left)
      let r = optimize(right)
      match (l, r) {
        (Identity, r) => r
        (l, Identity) => l
        _ => Pipe(l, r)
      }
    }
    Comma(left, right) => Comma(optimize(left), optimize(right))
    Paren(inner) => optimize(inner)
    ArrayConstruct(Some(inner)) => ArrayConstruct(Some(optimize(inner)))
    ObjectConstruct(fields) => {
      let opt_fields : Array[(ObjKey, Filter)] = []
      for i = 0; i < fields.length(); i = i + 1 {
        let (key, value) = fields[i]
        let opt_key = match key {
          ExprKey(expr) => ExprKey(optimize(expr))
          _ => key
        }
        opt_fields.push((opt_key, optimize(value)))
      }
      ObjectConstruct(opt_fields)
    }
    Try(inner) => Try(optimize(inner))
    TryCatch(inner, handler) => TryCatch(optimize(inner), optimize(handler))
    IfThenElse(cond, then_b, else_b) =>
      IfThenElse(optimize(cond), optimize(then_b), optimize(else_b))
    Binding(pat, expr, body) => Binding(pat, optimize(expr), optimize(body))
    Reduce(iter, pat, init, update) =>
      Reduce(optimize(iter), pat, optimize(init), optimize(update))
    FuncDef(name, params, body, rest) =>
      FuncDef(name, params, optimize(body), optimize(rest))
    FuncCall(name, args) => {
      let opt_args : Array[Filter] = []
      for i = 0; i < args.length(); i = i + 1 {
        opt_args.push(optimize(args[i]))
      }
      FuncCall(name, opt_args)
    }
    Neg(inner) => Neg(optimize(inner))
    Arith(op, left, right) => Arith(op, optimize(left), optimize(right))
    Compare(op, left, right) => Compare(op, optimize(left), optimize(right))
    LogicAnd(left, right) => LogicAnd(optimize(left), optimize(right))
    LogicOr(left, right) => LogicOr(optimize(left), optimize(right))
    DynIndex(expr) => DynIndex(optimize(expr))
    PostfixDynIndex(base, idx) => PostfixDynIndex(optimize(base), optimize(idx))
    DynSlice(base, start, end) =>
      DynSlice(optimize(base), start.map(optimize), end.map(optimize))
    Alternative(left, right) => Alternative(optimize(left), optimize(right))
    Foreach(iter, pat, init, update, extract) =>
      Foreach(
        optimize(iter),
        pat,
        optimize(init),
        optimize(update),
        extract.map(optimize),
      )
    StringInterp(parts) => {
      let opt_parts : Array[(String, Filter?)] = []
      for part in parts {
        let (lit, filter_opt) = part
        opt_parts.push(
          (
            lit,
            match filter_opt {
              Some(f) => Some(optimize(f))
              None => None
            },
          ),
        )
      }
      StringInterp(opt_parts)
    }
    Assign(path_expr, value_expr) =>
      Assign(optimize(path_expr), optimize(value_expr))
    Update(path_expr, update_filter) =>
      Update(optimize(path_expr), optimize(update_filter))
    UpdateAlt(path_expr, value_expr) =>
      UpdateAlt(optimize(path_expr), optimize(value_expr))
    Label(name, body) => Label(name, optimize(body))
    Break(_) => filter
    BindingAlt(patterns, expr, body) =>
      BindingAlt(patterns, optimize(expr), optimize(body))
    _ => filter
  }
}