public OverloadedFunction add(AbstractFunction candidate) {
    List<AbstractFunction> joined = new ArrayList<AbstractFunction>(primaryCandidates.size() + 1);
    joined.addAll(primaryCandidates);
    List<AbstractFunction> defJoined =
        new ArrayList<AbstractFunction>(defaultCandidates.size() + 1);
    defJoined.addAll(defaultCandidates);

    if (candidate.isDefault() && !defJoined.contains(candidate)) {
      defJoined.add(candidate);
    } else if (!candidate.isDefault() && !joined.contains(candidate)) {
      joined.add(candidate);
    }

    return new OverloadedFunction(
        "(" + name + "+" + candidate.getName() + ")",
        lub(joined).lub(lub(defJoined)),
        joined,
        defJoined,
        ctx);
  }
  public OverloadedFunction(AbstractFunction function) {
    super(function.getType(), null, function.getEval());

    this.name = function.getName();

    this.primaryCandidates = new ArrayList<AbstractFunction>(1);
    this.defaultCandidates = new ArrayList<AbstractFunction>(1);

    if (function.isDefault()) {
      defaultCandidates.add(function);
    } else {
      primaryCandidates.add(function);
    }

    this.isStatic = function.isStatic();
  }
  /**
   * This function groups occurrences of pattern dispatched functions as one "PatternFunction" that
   * has a hash table to look up based on outermost function symbol. A group is a bunch of functions
   * that have an ADT as a first parameter type and a call or tree pattern with a fixed name as the
   * first parameter pattern, or it is a singleton other case. The addAll function retains the order
   * of the functions from the candidates list, in order to preserve shadowing rules!
   */
  private void addAll(
      List<AbstractFunction> container, List<AbstractFunction> candidates, boolean nonDefault) {
    @SuppressWarnings("unchecked")
    Map<String, List<AbstractFunction>>[] constructors = new Map[10];
    @SuppressWarnings("unchecked")
    Map<IConstructor, List<AbstractFunction>>[] productions = new Map[10];
    List<AbstractFunction> other = new LinkedList<AbstractFunction>();

    for (AbstractFunction func : candidates) {
      if (nonDefault && func.isDefault()) {
        continue;
      }
      if (!nonDefault && !func.isDefault()) {
        continue;
      }

      String label = null;
      IConstructor prod = null;

      if (func.isPatternDispatched()) {
        // this one is already hashed, but we might find more to add to that map in the next round
        Map<String, List<AbstractFunction>> funcMap =
            ((AbstractPatternDispatchedFunction) func).getMap();
        int pos = func.getIndexedArgumentPosition();

        for (String key : funcMap.keySet()) {
          addFuncsToMap(pos, constructors, funcMap.get(key), key);
        }
      } else if (func.isConcretePatternDispatched()) {
        // this one is already hashed, but we might find more to add to that map in the next round
        Map<IConstructor, List<AbstractFunction>> funcMap =
            ((ConcretePatternDispatchedFunction) func).getMap();
        int pos = func.getIndexedArgumentPosition();

        for (IConstructor key : funcMap.keySet()) {
          addProdsToMap(pos, productions, funcMap.get(key), key);
        }
      } else {
        // a new function definition, that may be hashable
        int pos = func.getIndexedArgumentPosition();
        label = func.getIndexedLabel();
        prod = func.getIndexedProduction();

        if (label != null) {
          // we found another one to hash
          addFuncToMap(pos, constructors, func, label);
        } else if (prod != null) {
          addProdToMap(pos, productions, func, prod);
        } else {
          other.add(func);
        }
      }
    }

    for (int i = 0; i < constructors.length; i++) {
      if (constructors[i] != null && !constructors[i].isEmpty()) {
        container.add(
            new AbstractPatternDispatchedFunction(
                ctx.getEvaluator(), i, name, type, constructors[i]));
      }
    }

    for (int i = 0; i < productions.length; i++) {
      if (productions[i] != null && !productions[i].isEmpty()) {
        container.add(
            new ConcretePatternDispatchedFunction(
                ctx.getEvaluator(), i, name, type, productions[i]));
      }
    }

    container.addAll(other);
  }