Skip to content

Built-in list functions do not allow variable argument lists #26

@PaulChernoch-Shell

Description

@PaulChernoch-Shell

The built-in list functions max, min, sum and mean currently only accept a single argument, a list of items to be aggregated.

The DMN 1.2 specification, section 10.3.4.4 permits a flattened list of arguments of arbitrary length to be supplied instead of a list.

For example both these should work:

  • max([1,2,3]) = 3
  • max(1,2,3) = 3

The following proposed change to utils\built-in-functions\list-functions\index.js would accomplish this.

Replace min, max, sum, and mean imlementations with this:

// Heuristic to determine if an object is a context or not.
// If so, it will have had the built-in functions added to it.
function isContext(obj) {
  return 'max' in obj && 'substring' in obj;
}

const aggregateHelper = (aggregatorFunc, itemsOrList) => {
  if (itemsOrList.length === 1) {
    // One item in array, so require it to be an array.
    if (!Array.isArray(itemsOrList[0])) {
      try {
        return aggregatorFunc([itemsOrList[0]]);
      } catch (err) {
        throw new Error('operation unsupported on element of this type');
      }
    } else {
      return aggregatorFunc(itemsOrList[0]);
    }
  } else if (itemsOrList.length === 2 && isContext(itemsOrList[1])) {
    // Two items in array, but the second one is a context, so only aggregate the first.
    try {
      if (!Array.isArray(itemsOrList[0])) {
        return aggregatorFunc([itemsOrList[0]]);
      }
      return aggregatorFunc(itemsOrList[0]);
    } catch (err) {
      throw new Error('operation unsupported on element of this type');
    }
  } else {
    try {
      if (isContext(itemsOrList[itemsOrList.length - 1])) {
        return aggregatorFunc(itemsOrList.slice(0, itemsOrList.length - 1));
      }
      return aggregatorFunc(itemsOrList);
    } catch (err) {
      throw new Error('operation unsupported on element of this type');
    }
  }
};

// Permits min(n1, n2, n3, ...) or min(list)
const min = (...itemsOrList) => aggregateHelper(arr => _.min(arr), itemsOrList);

// Permits max(n1, n2, n3, ...) or max(list)
const max = (...itemsOrList) => aggregateHelper(arr => _.max(arr), itemsOrList);

// Permits sum(n1, n2, n3, ...) or sum(list)
const sum = (...itemsOrList) => aggregateHelper(arr => _.sum(arr), itemsOrList);

const flatCount = (...itemsOrList) => aggregateHelper(arr => arr.length, itemsOrList);

// Permits mean(n1, n2, n3, ...) or mean(list)
const mean = (...itemsOrList) => {
  const itemSum = sum(...itemsOrList);
  const itemCount = flatCount(...itemsOrList);
  return itemSum / itemCount;
};

NOTE on aggregateHelper:

This function works around the implementation details. The context object is passed to all these methods as a hidden extra argument. By using the rest operator to get all arguments (since the arguments variable is not available in a lambda function) we are also slurping up that context, if present. This function checks for the context and removes it from the arguments to be passed on to lodash.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions