src/utils.js

  1. const { isArray } = Array;
  2. import isObject from './functional/isObject.js';
  3. import isString from './functional/isString.js';
  4. import isUndefined from './functional/isUndefined.js';
  5. import property from './functional/property.js';
  6. import { isLensClass, at_maybe } from '../src-cjs/constants.js';
  7. export const isLens = property(isLensClass);
  8. export const $iterator = property(Symbol.iterator);
  9. export function index_maybe(subject, key) {
  10. return isObject(subject) ? subject[at_maybe](key) : {};
  11. }
  12. export function getIterator(val) {
  13. if (isString(val)) {
  14. return;
  15. }
  16. return $iterator(val);
  17. }
  18. /**
  19. * @typedef {Array} FoundPair
  20. * @property {*} 0 Value of found item
  21. * @property {number|string} [1] Index or key of found item
  22. */
  23. /**
  24. * @generator
  25. * @function module:natural-lenses#eachFound
  26. * @summary Iterate over Maybe monad contained value(s)
  27. * @param {Maybe.<*>} maybe_val Maybe monad of value or iterable value
  28. * @yields {FoundPair} Pairs of value and key
  29. * @see {@link module:natural-lenses#maybeDo}
  30. *
  31. * @description
  32. * When called on the result of [getting a multifocal into a Maybe monad]{@link AbstractNFocal#get_maybe},
  33. * this function iterates found values (and only the found values), yielding the
  34. * pair of the value and index for each, like the arguments to the callback of
  35. * `Array.prototype.forEach` if it used "rest" syntax, e.g.
  36. * ```js
  37. * values.forEach((...pair) => {
  38. * const [value, index] = pair;
  39. * // do something with index and value
  40. * });
  41. * ```
  42. * When called on the `get_maybe`-result of an {@link ArrayNFocal}, the index 1
  43. * value of each yielded Array will be an integer index; with an {@link ObjectNFocal},
  44. * the index 1 value of each yeilded Array will be a string key.
  45. *
  46. * This function can also be applied to a {@link Maybe} monad value obtained from
  47. * a monofocal optic (e.g. a {@link Lens}), in which case it yields either a
  48. * single element array containing the value if the input holds a `just` property
  49. * and does not yield, otherwise. It is more flexible to apply
  50. * [maybeDo]{@link module:natural-lenses#maybeDo} or {@link Optic#getting}, as
  51. * these allow separate handling of the *Nothing* case.
  52. */
  53. export function eachFound(maybe_val) {
  54. if (!('just' in maybe_val)) {
  55. return makeIterable(() => ({done: true}));
  56. }
  57. const val = maybe_val.just;
  58. if (!maybe_val.multiFocal) {
  59. let i = 0;
  60. return makeIterable(() => {
  61. if (i === 0) {
  62. ++i;
  63. return {done: false, value: [val]};
  64. } else {
  65. return {done: true};
  66. }
  67. });
  68. }
  69. if (isArray(val)) {
  70. let i = 0;
  71. return makeIterable(() => {
  72. while (i < val.length && !(i in val)) {
  73. ++i;
  74. }
  75. if (i < val.length) {
  76. const iterVal = [val[i], i];
  77. ++i;
  78. return {done: false, value: iterVal};
  79. } else {
  80. return {done: true};
  81. }
  82. });
  83. } else /* istanbul ignore else: unsupported case */ if (isObject(val)) {
  84. const entries = Object.entries(val);
  85. let i = 0;
  86. return makeIterable(() => {
  87. if (i >= entries.length) {
  88. return {done: true};
  89. }
  90. const iterVal = {done: false, value: entries[i].reverse()};
  91. ++i;
  92. return iterVal;
  93. });
  94. } else {
  95. let i = 0;
  96. return makeIterable(() => {
  97. if (i === 0) {
  98. ++i;
  99. return {done: false, value: [val]};
  100. } else {
  101. return {done: true};
  102. }
  103. });
  104. }
  105. }
  106. function makeIterable(next) {
  107. return {[Symbol.iterator]: () => ({ next })};
  108. }
  109. /**
  110. * @template T, U
  111. * @function module:natural-lenses#maybeDo
  112. * @summary Conditionally execute a function based on the construction of a {@link Maybe}
  113. * @param {Maybe.<T>} maybe The input value determining which of the following arguments is invoked
  114. * @param {function(T): U} then The function executed with the `just` value of *maybe*, if present
  115. * @param {function(): U} [orElse] The function executed if *maybe* contains no `just` property
  116. * @returns {U} The result type of the invoked function
  117. * @see Optic#getting
  118. *
  119. * @description
  120. * This function resembles an `if` statement around the "*Just*-ness" of a {@link Maybe}
  121. * value: the *then* Function gets called if *maybe* has a `just` and the *orElse*
  122. * Function if not. Because of the usual intent of this conditional situation,
  123. * `maybe.just` value is passed to *then* if *then* is called.
  124. *
  125. * Whichever of *then* or *orElse* is called, its return value becomes the return
  126. * value of this function call.
  127. */
  128. export function maybeDo(maybe, then, orElse) {
  129. return ('just' in maybe) ? then(maybe.just) : (orElse ? orElse() : undefined);
  130. }
  131. export const lensCap = {
  132. [isLensClass]: true,
  133. get: function () {},
  134. get_maybe: function() {return {};}
  135. };
  136. export function incorporateStdlibSupport(targetClass, methods) {
  137. const classProto = targetClass.prototype;
  138. methods.forEach(([sym, method]) => {
  139. if (!classProto.hasOwnProperty(sym)) {
  140. Object.defineProperty(classProto, sym, {
  141. configurable: true,
  142. writable: true,
  143. value: method,
  144. });
  145. }
  146. });
  147. }
  148. export function handleNoniterableValue(excVal, maybeVal) {
  149. if (isUndefined(excVal) || !('just' in maybeVal)) {
  150. return;
  151. }
  152. if (isObject(excVal)) {
  153. excVal.noniterableValue = maybeVal.just;
  154. }
  155. throw excVal;
  156. }