src/optic_array.js

import Optic from './optic.js';
import { isLens, lensCap } from './utils.js';

/**
 * @extends Optic
 */
class OpticArray extends Optic {
  /**
   * @summary Aggregation of multiple lens applied in series
   *
   * @description
   * Construct this using {@link module:natural-lenses#fuse}.
   */
  constructor(lenses) {
    super();
    this.lenses = lenses;
  }

  /**
   * @inheritdoc
   */
  present(subject) {
    if (this.lenses.length === 0) return true;
    const throughLenses = this.lenses.slice(), finalLens = throughLenses.pop();
    const rval = throughLenses.reduce(
      (subject, lens) => lens.get(subject),
      subject
    );
    return finalLens.present(rval);
  }

  /**
   * @inheritdoc
   */
  get(subject, ...tail) {
    const subjResult = Array.prototype.reduce.call(
      this.lenses,
      (subject, lens) => lens.get(subject),
      subject
    );
    if (tail.length > 0) {
      return isLens(subjResult) ? subjResult.get(...tail) : undefined
    }
    return subjResult;
  }

  /**
   * @inheritdoc
   */
  get_maybe(subject, ...tail) {
    const stepSubject = get_maybe_internal.call(this, {just: subject});
    const subjResult = stepSubject.just;
    if (tail.length > 0) {
      return isLens(subjResult) ? subjResult.get_maybe(...tail) : {};
    }
    return stepSubject;
  }

  /**
   * @inheritdoc
   */
  xformInClone_maybe(subject, fn) {
    var i;
    const lensSubjects = new Array(this.lenses.length);
    lensSubjects[0] = {just: subject};
    
    const leadingLensCount = this.lenses.length - 1;
    for (i = 0; i < leadingLensCount; i++) {
      const l = this.lenses[i];
      lensSubjects[i + 1] = l.get_maybe(lensSubjects[i].just);
      if (!('just' in lensSubjects[i + 1])) {
        break;
      }
    }
    
    const xformResults = new Array(this.lenses.length);
    i = this.lenses.length - 1;
    const xformInput_maybe = (lensSubjects[i] || {});
    xformResults[i] = this.lenses[i].xformInClone_maybe(xformInput_maybe.just, fn);
    if ('just' in xformInput_maybe) {
      if (xformResults[i] === xformInput_maybe.just) {
        return subject;
      }
    }
    for (i = leadingLensCount - 1; i >= 0; i--) {
      let lensSubject = lensSubjects[i] || {};
      xformResults[i] = this.lenses[i].setInClone(lensSubject.just, xformResults[i + 1]);
    }
    return xformResults[0];
  }
}

export default OpticArray;

/**
 * @private
 * @this OpticArray
 * @param {Maybe.<*>} subject_maybe
 * @returns {Maybe.<*>}
 */
function get_maybe_internal(subject_maybe) {
  let stepSubject = subject_maybe;
  for (let i = 0; i < this.lenses.length; i++) {
    const lens = this.lenses[i];
    stepSubject = lens.get_maybe(stepSubject.just);
    if (!('just' in stepSubject)) {
      return {};
    }
  }
  return stepSubject;
}