/** * @module dataflow - a dataflow abstraction that is not based on concurrency but on laziness and * can be used in an asynchronous fashion. */ /** * @callback createValueCallback * @template _T_ * @type { () => _T_ } */ /** * A dataflow abstraction that takes a function that specifies how to create a value and returns a * function that returns that value. The callback will be only called when needed and not more than once. * In other contexts known as "lazy" or "thunk". * @template _T_ * @param { !createValueCallback } createValue - will be called when needed and not more than once. Mandatory. * @return { () => _T_ } * @constructor * @example * const x = DataFlowVariable(() => y() + 1); * const y = DataFlowVariable(() => 1); * x() === 2 */ const DataFlowVariable = createValue => { let value = undefined; return () => { if (value !== undefined) { return value } value = createValue(); return value; } }; /** * @callback onResolveCallback * @impure sets the surrounding {@link Promise} to the "resolved" state. * @type { () => void } */ /** * @typedef { (onResolveCallback) => void } Task * @impure can produce arbitrary side effects and must use the {@link onResolveCallback} to signal completion. */ /** * @typedef { object } SchedulerType * @property { (Task) => void } add - schedule the task for execution. * The {@link Task} must call its {@link onResolveCallback} when finished. * @property { Function } addOk - convenience function that adds the {@link Task} for execution * and calls "ok" {@link onResolveCallback} after execution no matter what. */ /** * Constructing a new {@link SchedulerType } where {@link Task}s can be added for asynchronous but sequence-preserving * execution. That means that even though the scheduled tasks can run asynchronously, it is still guaranteed that * when first task A and then task B is added, tasks B will not be started before task A has finished. * Note that this scheduler has no timeout facility and an async {@link Task} that never calls its * {@link onResolveCallback} will stall any further task execution. * @return { SchedulerType } * @constructor * @example * const scheduler = Scheduler(); * scheduler.add( ok => { * setTimeout( _ => { * console.log("A"); * ok(); * }, 100); * }); * scheduler.addOk ( () => console.log("B")); * // log contains first A, then B */ const Scheduler = () => { let inProcess = false; const tasks = []; function process() { if (inProcess) return; if (tasks.length === 0) return; inProcess = true; const task = tasks.pop(); const prom = new Promise( ok => task(ok) ); prom.then( _ => { inProcess = false; process(); }); } function add(task) { tasks.unshift(task); process(); } return { add: add, addOk: task => add( ok => { task(); ok(); }) // convenience } };/** * @module util/arrayFunctions * Utility module for array-dependent functions. */ /** * A function that compares two arrays for equality by checking that they are of the same length and * all elements are pairwise equal with respect to the "===" operator. Arguments are given in curried style. * Arguments must not be null/undefined and must be of type {@link Array}. * @template T * @pure * @complexity O(n) * @haskell [a] -> [a] -> bool * @function arrayEq * @type { (arrayA:!Array) => (arrayB:!Array) => boolean } * @param { !Array} arrayA - the first array. Mandatory. * @returns { (arrayB:!Array) => boolean} * @example * arrayEq ([]) ([]) === true; * arrayEq ([1]) ([2]) === false; */ const arrayEq = arrayA => arrayB => arrayA.length === arrayB.length && arrayA.every( (it, idx) => it === arrayB[idx]); /** * From the {@link array}, remove the item at position "index". The arguments are given in curried style. * The index must be >= 0 and < `array.length` or nothing is removed and an empty array is returned. * @impure Since the given array is modified. * @function removeAt * @template T * @type { (array:!Array) => (index:!number) => Array } * @param { !Array} array - the array to remove from. Mandatory. * @returns { (index:!number) => Array } - finally, the removed element is returned in a singleton array, or an empty array in case nothing was removed, see {@link splice} * @example * const array = [1,2,3]; * removeAt(array)(0); * arrayEq(array)([2,3]); */ const removeAt = array => index => array.splice(index, 1); /** * From the {@link array}, remove the "item". The arguments are given in curried style. * In case that the item occurs multiple times in the array, only the first occurrence is removed. * @impure Since the given array is modified. * @function removeItem * @template T * @type { (array:!Array) => (item:!T) => Array } * @param { !Array} array - the array to remove from. Mandatory. * @returns { (item:!T) => Array } - finally, the removed element is returned in a singleton array or an empty array in case nothing was removed, see {@link splice} * @example * const array = ["a","b","c"]; * removeItem(array)("b"); * arrayEq(array)(["a","c"]); */ const removeItem = array => item => { const i = array.indexOf(item); if (i >= 0) { return removeAt(array)(i); } return []; }; /** * @typedef { <_T_> (!Number) => _T_ } TimesCallback<_T_> */ /** * A function that executes the optional {@link TimesCallback} "soMany" times, assembles the results and returns them in an * {@link array} of length "soMany". The arguments are given in curried style. * If no callback is given, the unaltered index is returned. Indexes go from 0 to soMany-1. * @impure if the callback is impure * @haskell Int -> (Int -> a) -> [a] * @function times * @type { <_T_> (soMany: !Number) => (cb: ?TimesCallback) => Array<_T_> } * soMany - how often to execute the callback. Negative values will be treated like 0. Mandatory. * @throws { TypeError } - if soMany is given as a String but does not represent a number * @example * times(3)(i => console.log(i)); // logs 0, 1, 2 * times(5)(x=>x*x); // returns [0, 1, 4, 9, 16] */ const times = soMany => (callback) => { const number = Number(soMany.valueOf()); if (isNaN(number)) { throw new TypeError("Object '" + soMany + "' is not a valid number."); } return Array.from({length: number}, (it, idx) => callback ? callback(idx) : idx); }; /** * @typedef { <_T_> (!_T_, index: ?Number) => Number } SumCallback<_T_> */ /** * A function that sums up all items from an {@link array} by applying the {@link SumCallback} to each item before adding up. * The arguments are given in curried style. * @impure if the callback is impure * @haskell Num n => [a] -> (a -> n) -> n * @type { <_T_> (array:!Array<_T_>) => (cb: ProducerType | ?SumCallback<_T_>) => Number } * @example * sum([1,2,3])() === 1 + 2 + 3; * sum(["1"])(Number) === 1; */ const sum = array => (callback = Number) => { const cb = /** @type {ProducerType | ?SumCallback} */ callback; return array.reduce( (acc, cur, idx) => acc + cb(cur, idx), 0); };/** * @module util/array * Augmenting the {@link Array}, {@link String}, and {@link Number} prototypes with functions from the arrayFunctions module. * These functions live in their own module such that users of the library can keep their code clean * from prototype modifications if they prefer to do so. */ /** * See {@link arrayEq}. * @template _T_ * @param { Array<_T_> } array * @return { Boolean } * @example * [1].eq([1]); // true */ Array.prototype.eq = function(array) { return arrayEq(this)(array);}; /** * See {@link removeAt}. * @template _T_ * @impure Modifies the array instance. * @param { Number } index * @return { Array<_T_> } * @example * [1,2,3].removeAt(0); */ Array.prototype.removeAt = function(index){ return removeAt(this)(index); }; /** * See {@link removeItem}. * @template _T_ * @impure Modifies the array instance. * @param { _T_ } item * @return { Array<_T_> } * @example * ["a","b","c"].removeItem("b"); */ Array.prototype.removeItem = function(item){ return removeItem(this)(item); }; /** * See {@link times}. * @template _T_ * @param { ?TimesCallback } callback * @return { Array<_T_> } * @example * "10".times(it => console.log(it)); */ String.prototype.times = function(callback = undefined){ return times(this)(callback); }; /** * See {@link times}. * @template _T_ * @param { ?TimesCallback } callback * @return { Array<_T_> } * @example * (5).times(x => x * x); // [0, 1, 4, 9, 16] */ Number.prototype.times = function(callback= undefined){ return times(this)(callback); }; /** * See {@link sum}. * @param { ?SumCallback } callback * @return { Number } * @example * [1,2,3].sum(); // 6 * ["1"].sum(Number); // 1 */ Array.prototype.sum = function(callback = undefined){ return sum(this)(callback); };/** * @typedef { <_T_> (newValue:_T_, oldValue: ?_T_) => void } ValueChangeCallback<_T_> * This is a specialized {@link ConsumerType} with an optional second value. * The "oldValue" contains the value before the change. */ /** * IObservable<_T_> is the interface from the GoF Observable design pattern. * In this variant, we allow to register many observers but do not provide means to unregister. * Observers are not garbage-collected before the observable itself is collected. * IObservables are intended to be used with the concept of "stable binding", i.e. with * listeners that do not change after setup. * @typedef IObservable<_T_> * @template _T_ * @impure Observables change their inner state (value) and maintain a list of observers that changes over time. * @property { () => _T_ } getValue - a function that returns the current value * @property { (_T_) => void} setValue - a function that sets a new value, calling all registered {@link ValueChangeCallback}s * @property { (cb: ValueChangeCallback<_T_>) => void } onChange - * a function that registers an {@link ValueChangeCallback} that will be called whenever the value changes. * Immediately called back on registration. */ /** * Constructor for an IObservable<_T_>. * @pure * @template _T_ * @param {!_T_} value - the initial value to set. Mandatory. * @returns { IObservable<_T_> } * @constructor * @example * const obs = Observable(""); * obs.onChange(val => console.log(val)); * obs.setValue("some other value"); // will be logged */ const Observable = value => { const listeners = []; return { onChange: callback => { listeners.push(callback); callback(value, value); }, getValue: () => value, setValue: newValue => { if (value === newValue) return; const oldValue = value; value = newValue; listeners.forEach(callback => { if (value === newValue) { // pre-ordered listeners might have changed this and thus the callback no longer applies callback(value, oldValue); } }); } } }; /** * IObservableList<_T_> is the interface for lists that can be observed for add or delete operations. * In this variant, we allow registering and unregistering many observers. * Observers that are still registered are not garbage collected before the observable list itself is collected. * @typedef IObservableList * @template _T_ * @impure Observables change their inner decorated list and maintain two lists of observers that changes over time. * @property { (cb:ConsumerType<_T_>) => void } onAdd - register an observer that is called whenever an item is added. * @property { (cb:ConsumerType<_T_>) => void } onDel - register an observer that is called whenever an item is added. * @property { (_T_) => void } add - add an item to the observable list and notify the observers. Modifies the list. * @property { (_T_) => void } del - delete an item to the observable list and notify the observers. Modifies the list. * @property { (cb:ConsumerType<_T_>) => void } removeAddListener - unregister the "add" observer * @property { (cb:ConsumerType<_T_>) => void } removeDeleteListener - unregister the "delete" observer * @property { () => Number } count - current length of the inner list. * @property { (cb:ConsumingPredicateType<_T_>) => Number } countIf - number of items in the list that satisfy the given predicate. */ /** * Constructor for an IObservableList<_T_>. * @pure * @template _T_ * @param {!Array<_T_>} list - the inner list that is to be decorated with observability. Mandatory. See also GoF decorator pattern. * @returns IObservableList<_T_> * @constructor * @example * const list = ObservableList( [] ); * list.onAdd( item => console.log(item)); * list.add(1); */ const ObservableList = list => { const addListeners = []; const delListeners = []; const removeAddListener = addListener => addListeners.removeItem(addListener); const removeDeleteListener = delListener => delListeners.removeItem(delListener); return { onAdd: listener => addListeners.push(listener), onDel: listener => delListeners.push(listener), add: item => { list.push(item); addListeners.forEach( listener => listener(item)); }, del: item => { list.removeItem(item); const safeIterate = [...delListeners]; // shallow copy as we might change the listeners array while iterating safeIterate.forEach( listener => listener(item, () => removeDeleteListener(listener) )); }, removeAddListener, removeDeleteListener, count: () => list.length, countIf: pred => list.reduce( (sum, item) => pred(item) ? sum + 1 : sum, 0) } };/** * @module stdtypes * The js doc definitions of the types that are most commonly used. */ /** * @typedef { <_T_> (...x) => _T_ } ProducerType<_T_> * A function that takes arbitrary arguments (possibly none) and produces a value of type _T_. */ /** * @typedef { <_T_> (_T_) => void } ConsumerType<_T_> * A function that consumes a value of type _T_ and returns nothing. */ /** * @typedef { <_T_> (_T_) => Boolean } ConsumingPredicateType<_T_> * A function that consumes a value of type _T_ and returns a Boolean. */ /** * @typedef { <_T_> (_T_) => _T_ } UnaryOperatorType<_T_> * A unary operator on _T_. */ /** * A callback which takes one argument of type {@link _A_} and transforms it to {@link _B_}. * @template _A_ * @template _B_ * @callback Functor * @param { _A_ } value * @returns { _B_ } */ /** * A callback which takes two arguments of type {@link _A_} and transforms it to {@link _A_}. * @template _A_ * @callback BiOperation * @param { _A_ } value1 * @param { _A_ } value2 * @returns { _A_ } */ /** * A callback which takes two arguments of type _T_ and _U_}and transforms it to _R_. * @callback BiFunction * @type { <_T_, _U_, _R_> (value1:_T_, value2:_U_) => _R_ } */ /** * A callback which takes an argument of type {@link _A_} and * a second argument of type {@link _A_} and returns a boolean. * @template _A_ * @template _B_ * @callback BiPredicate * @param { _A_ } value1 * @param { _B_ } value2 * @returns { boolean } */ /** * Defines a Monad. * @template _T_ * @typedef MonadType * @property { <_U_> (bindFn: (_T_) => MonadType<_U_>) => MonadType<_U_> } and * @property { <_U_> (f: (_T_) => _U_) => MonadType<_U_> } fmap * @property { (_T_) => MonadType<_T_> } pure * @property { () => MonadType<_T_> } empty *//** * A callback function that selects between two arguments that are given in curried style. * Only needed internally for the sake of proper JsDoc. * @callback PairSelectorType * @pure * @template _T_, _U_ * @type { <_T_, _U_> (x:_T_) => (y:_U_) => ( _T_ | _U_ ) } * @property { () => { next: () => IteratorResult<_T_ | _U_, undefined> } } Symbol.iterator */ /** * @typedef PairBaseType * @template _T_, _U_ * @type { * (x: _T_) * => (y: _U_) * => (s: PairSelectorType<_T_, _U_>) => ( _T_ | _U_ ) } * */ /** * @typedef PairType * @template _T_, _U_ * @type { PairBaseType<_T_, _U_> & Iterable<_T_ | _U_> } * see {@link Pair} */ /** * A Pair is a {@link Tuple}(2) with a smaller and specialized implementation. * Access functions are {@link fst} and {@link snd}. Pairs are immutable. * "V" in the SKI calculus, or "Vireo" in the Smullyan bird metaphors. * * @constructor * @pure * @haskell a -> b -> (a -> b -> a|b) -> a|b * @template _T_, _U_ * @type { PairType<_T_, _U_> } * * @example * const values = Pair("Tobi")("Andri"); * values(fst) === "Tobi"; * values(snd) === "Andri"; * * // a pair is also iterable * const [tobi, andri] = values; * console.log(tobi, andri); * // => Logs '"Tobi", "Andri"' */ const Pair = x => y => { /** * @template _T_, _U_ * @type { PairSelectorType<_T_,_U_> } */ const pair = selector => selector(x)(y); pair[Symbol.iterator] = () => [x,y][Symbol.iterator](); return pair; };// noinspection GrazieInspection /** * JINQ brings query capabilities to any monadic type. * With it, it is possible to query different data sources using the one and same query language. * * @template _T_ * @typedef JinqType * @property { <_U_> (selector: Functor<_T_, _U_>) => JinqType<_U_> } map - maps the current value to a new value * @property { <_U_> (selector: Functor<_T_, _U_>) => JinqType<_U_> } select - alias for map * @property { <_U_> ((prev: _T_) => MonadType<_U_>) => JinqType<_U_> } inside - maps the current value to a new {@link MonadType} * @property { <_U_> (monad: MonadType<_U_>) => JinqType> } pairWith - combines the underlying data structure with the given data structure as {@link PairType} * @property { (predicate: ConsumingPredicateType<_T_>) => JinqType<_T_> } where - only keeps the items that fulfill the predicate * @property { () => MonadType<_T_> } result - returns the result of this query */ /** * JINQ (JavaScript integrated query) is the implementation of LINQ for JavaScript. * It can handle any data that conforms to the {@link MonadType}. Therefore, JINQ can * handle monadic iterables like {@link SequenceType} and every monadic type such as * {@link MaybeType} or {@link JsonMonad}. * * Note: Despite the similarity to SQL, it is important to note that the functionalities are not exactly the same. * * @see https://learn.microsoft.com/en-us/dotnet/csharp/linq/ * @template _T_ * @param { MonadType<_T_> } monad * @returns { JinqType<_T_> } * * @example * const devs = [ {"name": "Tobi", "salary": 0}, * {"name": "Michael", "salary": 50000}, * ... * ] * const salaryOfMichael = devs => * from(JsonMonad(devs)) * .where( dev => dev.name != null) * .where( dev => dev.name.startsWith("Michael")) * .select(dev => dev.salary) * .result(); */ const jinq = monad => ({ pairWith: pairWith(monad), where: where (monad), select: select (monad), map: map$1 (monad), inside: inside (monad), result: () => monad }); /** * Serves as starting point to enter JINQ and specifies a data source. * Consider {@link jinq} linked below for further information. * * @see {jinq} * @template _T_ * @param { MonadType<_T_> } monad * @returns { JinqType<_T_> } * * @example * const range = Range(7); * const result = * from(range) * .where(x => x % 2 === 0) * .result(); * * console.log(result); * // => Logs '0 2 4 6' */ const from = jinq; /** * Transforms each element of a collection using a selector function. * * @template _T_, _U_ * @type { * (monad1: MonadType<_T_>) * => (selector: (_T_) => MonadType<_U_>) * => JinqType> * } * * @example * const Person = (name, maybeBoss) => ({name, boss: maybeBoss}); * const ceo = Person("Paul", Nothing); * const cto = Person("Tom", Just(ceo)); * const andri = Person("Andri", Just(cto)); * * const maybeBossNameOfBoss = employee => * from(Just(employee)) * .inside(p => p.boss) * .inside(p => p.boss) * .select(p => p.name) * .result(); * * const maybeName = maybeBossNameOfBoss(andri); * const noName = maybeBossNameOfBoss(ceo); * * assert.is(noName, Nothing); * maybeName * (_ => console.log("No valid result")) * (name => console.log(name); * * // => Logs 'Paul' * */ const inside = monad => f => { const processed = monad.and(f); return jinq(processed); }; /** * Combines elementwise two {@link MonadType}s. * It returns a {@link PairType} which holds a combination of two values. * * @template _T_, _U_ * @type { * (monad1: MonadType<_T_>) * => (monad2: MonadType<_U_>) * => JinqType> * } * * @example * const range = Range(3); * const result = * from(range) * .pairWith(range) * .where (([fst, snd]) => fst === snd) * .result(); * * console.log(result); * // => Logs '0 0 1 1 2 2 3 3' */ const pairWith = monad1 => monad2 => { const processed = monad1.and(x => monad2.fmap(y => Pair(x)(y)) ); return jinq(processed) }; /** * Filters elements based on a given condition. * * @type { <_T_> * (monad: MonadType<_T_>) * => (predicate: ConsumingPredicateType<_T_>) * => JinqType<_T_> * } * * @example * const range = Range(7); * const result = * from(range) * .where(x => x % 2 === 0) * .result(); * * console.log(result); * // => Logs '0 2 4 6' */ const where = monad => predicate => { const processed = monad.and(a => predicate(a) ? monad.pure(a) : monad.empty()); return jinq(processed); }; /** * Applies a function to each element of the collection. * * @alias map * @type { <_T_, _U_> * (monad: MonadType<_T_>) * => (selector: Functor<_T_, _U_>) * => JinqType<_U_> * } * * @example * const range = Range(3); * const result = * from(range) * .select(x => 2 * x) * .result(); * * console.log(result); * // => Logs '0, 2, 4, 6' */ const select = monad => mapper => { const processed = monad.fmap(mapper); return jinq(processed); }; /** * Applies a function to each element of the collection. * * @alias select * @type { <_T_, _U_> * (monad: MonadType<_T_>) * => (mapper: Functor<_T_, _U_>) * => JinqType<_U_> * } * * @example * const range = Range(3); * const result = * from(range) * .select(x => 2 * x) * .result(); * * console.log(result); * // => Logs '0, 2, 4, 6' */ const map$1 = select;/** * @module lambda/church * Church encoding of the lambda calculus in JavaScript * to the extent that we need it in Kolibri. * Recommended reading: https://en.wikipedia.org/wiki/Church_encoding . * Recommended watching: https://www.youtube.com/watch?v=6BnVo7EHO_8&t=1032s . */ /** * Identity function, aka "I" in the SKI calculus or "Ibis" (or "Idiot") in the Smullyan bird metaphors. * The function is pure and runs in O(1). Function calls can be inlined. * @haskell a -> a * @pure * @type { <_T_> (_T_) => _T_ } * @example * id(1) === 1 */ const id = x => x; /** * Constant function that captures and caches the argument and makes it available like a "getter". * Aka "konst", "fst" (the first of two curried parameters), * "K" in the SKI calculus, or "Kestrel" in the Smullyan bird metaphors. * @haskell a -> b -> a * @pure * @type { <_T_> (x:_T_) => (...y) => _T_ } * @example * c(1)(undefined) === 1; * const getExpr = c(expr); * // expression might change here but even if it does, the cached value will be returned * getExpr() === expr; */ const c = x => () => x; /** The first of two curried arguments, identical to {@link c} (see there for more info). * Often used to pick the first element of a {@link Pair}. * @type { <_T_> (x:_T_) => (...y) => _T_ } * @example * const point = Pair(1)(2); * point(fst) === 1; */ const fst = c; /** * A Function that returns the second of two curried arguments. * "KI" in the SKI calculus, or "Kite" in the Smullyan bird metaphors. * It can be seen as a cached getter for the id function: {@link c}({@link id}) * Often used to pick the first element of a {@link Pair}. * @haskell b -> a -> a * @pure * @type { <_T_> (...x) => (y:_T_) => _T_ } * @example * snd(undefined)(1) === 1; * const point = Pair(1)(2); * point(snd) === 2; */ const snd = (_=undefined) => y => y; // --------- ADT section --------- // private ADT implementation details --------------------- /** @private */ const TupleCtor = n => values => n === 0 // we have curried all ctor args, now ? Object.seal(selector => selector(values)) // return a function that waits for the selector : value => // there are still values to be curried TupleCtor (n - 1) ([...values, value]); // return the ctor for the remaining args /** @private */ const ChoiceCtor = position => n => choices => n === 0 // we have curried all ctor args, now ? Object.seal(choices[position] (choices[0]) ) // we call the chosen function with the ctor argument : choice => // there are still choices to be curried ChoiceCtor (position) (n - 1) ([...choices, choice]); // return the ctor for the remaining args // end of private section, publicly exported constructors follow /** * An n-Tuple stores n different values, which can be retrieved by accessor functions. * It is the most general form of a Product Type. Tuples are immutable. Values are accessed in O(1). * Since no indexes are managed by the user, there are no out-of-bounds errors. * @pure * @param {!Number} n - the cardinality, i.e. Tuple(n) can store n values. Mandatory. Must be > 0 or an error is thrown. * @return {Array} - an array where the first item is a constructor, follow by n accessor functions * @constructor * @example * const [Triple, one, two, three] = Tuple(3); * const triple = Triple(1)(2)(3); * triple(two) === 2; */ const Tuple = n => { if (n < 1) throw new Error("Tuple must have first argument n > 0"); return [ TupleCtor (n) ([]), // ctor curries all values and then waits for the selector // every selector is a function that picks the value from the curried ctor at the same position ...Array.from( {length:n}, (it, idx) => values => values[idx] ) ]; }; /** * A Choice selects between n distinct values, each of which can only be accessed if a * handling function is provided for each possible value. One cannot forget to handle edge cases. * It is the most general form of a CoProduct aka Sum Type. Choices are immutable. * @pure * @param {!Number} n - the cardinality, i.e. number of possible choices. Mandatory. Must be > 0 or an error is thrown. * @return {Array} - an array of n choice constructors * @constructor * @example * const [Bad, Good, Unknown] = Choice(3); * const guessWhat = Good(1); * guessWhat * (_ => console.error("this is bad")) // handle Bad case * (x => x) // handle Good case * (_ => 0); // Unknown -> default value */ const Choice = n => { // number of constructors if (n < 1) throw new Error("Choice must have first argument n > 0"); return Array.from( {length:n}, (it, idx) => ChoiceCtor (idx + 1) (n + 1) ([]) ) ; // n constructors with n curried args }; // end of private ADT implementation details /* * The Either types. * @haskell Either a b */ /** * A generic function from whatever type "a" to whatever "b". * Only needed internally for the sake of proper JsDoc. * @typedef FunctionAtoBType * @pure supposed to be pure * @type { <_T_, _U_> (x:_T_) => _U_ } */ /** * Type of the {@link Left} constructor after being bound to a value x of type _T_. * @typedef LeftXType * @type { <_T_, _U_> (f:FunctionAtoBType<_T_, _U_>) => (g:*) => _U_ } */ /** * The Left constructor of an Either type. An "Either" is either {@link Left} or {@link Right}. * It is constructed with a value of type "a" and waits for two more functions f and g * as curried arguments. * When both are given, f(x) is called. * The Left case of an Either type is usually (but not necessarily so) an error case. * Left values are immutable. * @haskell a -> (a -> b) -> c -> b * @pure if FunctionAtoBType is pure * @type { <_T_, _U_> (x:_T_) => LeftXType<_T_, _U_> } * @example * const withFoo = (null == foo) ? Left("could not find foo") : Right(foo); * withFoo * (msg => console.error(msg)) // handle left case * (x => doSomethingWithFoo(x)); // handle right case */ const Left = x => f => _g => f(x); /** * Type of the {@link Right} constructor after being bound to a value x of type _T_. * @typedef RightXType * @type { <_T_, _U_> (f:*) => (f:FunctionAtoBType<_T_, _U_>) => _U_ } */ /** * The Right constructor of an Either type. An "Either" is either {@link Left} or {@link Right}. * It is constructed with a value of type "b" and waits for two more functions f and g * as curried arguments. * When both are given, g(x) is called. * The Right case of an Either type is usually (but not necessarily so) the good case. * Right values are immutable. * @haskell a -> c -> (a -> b) -> b * @pure if FunctionAtoBType is pure * @type { <_T_, _U_> (x:_T_) => RightXType<_T_, _U_> } * @example * const withFoo = (null == foo) ? Left("could not find foo") : Right(foo); * withFoo * (msg => console.error(msg)) * (x => doSomethingWithFoo(x)); */ const Right = x => _f => g => g(x); /** * @typedef { LeftXType<_T_,_U_> | RightXType<_T_,_U_> } EitherType * @template _T_ * @template _U_ * @pure */ /** function application, beta reduction * @haskell (a -> b) -> a -> b * @pure if f is pure * @type { <_T_, _U_> (f: FunctionAtoBType<_T_, _U_>) => (x: _T_) => _U_ } * @example * beta(id)(42) === 42; */ const beta = f => x => f(x); /** An alternative name for the {@link c} function. */ const konst = c; /** * Flipping the sequence of two curried-style arguments. * Sometimes used to prepare for later eta reduction or make for more convenient use of f * when the x argument is a lengthy in-line expression. * @haskell (a -> b -> c) -> b -> a -> c * @type { <_T_, _U_, _V_> (f: (_T_) => (_U_) => _V_) => (_U_) => (_T_) => _V_ } */ const flip = f => x => y => f(y)(x); /** An alternative name for the {@link snd} function. */ const kite = snd; /** Composition of two functions, aka Bluebird (B) in the Smullyan bird metaphors. * @haskell (b -> c) -> (a -> b) -> a -> c * @type { <_T_, _U_, _V_> (f: FunctionAtoBType<_U_, _V_>) => (g: FunctionAtoBType<_T_, _U_>) => (x: _T_) => _V_ } */ const cmp = f => g => x => f(g(x)); /** * Composition of two functions f and g where g takes two arguments in curried style, * also known as Blackbird (BB) in the Smullyan bird metaphors. */ const cmp2 = f => g => x => y => f(g(x)(y)); // ---- boolean logic /** * True is the success case of the Boolean type. */ const T = fst; /** * False is the error case of the Boolean type. */ const F = snd; /** * A boolean value (True or False) in the Church encoding. * @typedef { T | F } ChurchBooleanType */ /** * Negating a boolean value. * @type { (x:ChurchBooleanType) => ChurchBooleanType } */ const not = x => x(F)(T); /** * The "and" operation for boolean values. * @type { (x:ChurchBooleanType) => (y:ChurchBooleanType) => ChurchBooleanType } */ const and = x => y => x(y)(x); /** * The "or" operation for boolean values. * @type { (x:ChurchBooleanType) => (y:ChurchBooleanType) => ChurchBooleanType } */ const or = x => x(x); /** * The boolean equivalence operation. * @type { (x:ChurchBooleanType) => (y:ChurchBooleanType) => ChurchBooleanType } */ const beq = x => y => x(y)(not(y)); /** * The boolean exclusive-or operation. * @type { (x:ChurchBooleanType) => (y:ChurchBooleanType) => ChurchBooleanType } */ const xor = x => y => cmp2 (not) (beq) (x) (y) ; // we cannot eta reduce since that messes up the jsDoc type inference /** * The boolean implication operation. * @type { (x:ChurchBooleanType) => (y:ChurchBooleanType) => ChurchBooleanType } */ const imp = x => flip(x) (not(x)) ; /** * Convert a boolean lambda expression to a javascript boolean value. * @type { (b:ChurchBooleanType) => Boolean } */ const jsBool = b => b(true)(false); /** * Convert a javascript boolean value to a boolean lambda expression. * @type { (jsB:Boolean) => ChurchBooleanType } */ const churchBool = jsB => /** @type {ChurchBooleanType} */ jsB ? T : F; /** * LazyIf makes a church boolean useful in an If-Then-Else construct where unlike the standard * JavaScript strict evaluation strategy the 'then' and 'else' cases are not evaluated eagerly but lazily. * To this end, the 'then' and 'else' cases must be wrapped in anonymous producer functions. * In other words: * LazyIf acts like a church boolean where we know that the result will be a function that we call without arguments. * * @type { <_T_> * (ChurchBooleanType) * => (f:FunctionAtoBType) * => (g:FunctionAtoBType) * => _T_ * } * @example * LazyIf( eq(n1)(n1) ) * ( _=> "same" ) * ( _=> "not same" ) */ const LazyIf = condition => thenFunction => elseFunction => ( condition(thenFunction)(elseFunction) )(); /** * Calling the function f recursively. * @type { <_T_> (f: (_T_) => _T_) => _T_ } */ const rec = f => f ( n => rec(f)(n) ) ; /** * @callback HandleLeftCallback * @type { <_T_,_U_,_V_> (l:LeftXType<_T_,_U_>) => _V_ } */ /** * @callback HandleRightCallback * @type { <_T_,_U_,_V_> (r:RightXType<_T_,_U_>) => _V_ } */ /** * Apply the f or g handling function to the Either value. * @type { <_T_,_U_,_V_> (e:EitherType<_T_,_U_>) => (hl:HandleLeftCallback<_T_,_U_>) => (hr:HandleRightCallback<_T_,_U_>) => _V_ } */ const either = e => f => g => e(f)(g); /** * @callback HandleNothingCallback * @template _T_,_U_ * @type { (n:NothingType<_T_>) => _U_ } */ /** * @callback HandleJustCallback * @template _T_,_U_ * @type { (j:JustType<_T_>) => _U_ } */ /** * Apply the f or g handling function to the Maybe value depending on whether it is a Just or a Nothing. * @type { <_T_,_U_> (m:MaybeType<_T_>) => (hn:HandleNothingCallback<_T_,_U_>) => (hj:HandleJustCallback<_T_,_U_>) => _U_ } */ const maybe = m => f => g => m(f)(g); /** * Take a function of two arguments and return a function of one argument that returns a function of one argument, * i.e. a function of two arguments in curried style. * @haskell curry :: ((a,b)->c) -> a -> b -> c * @type { <_T_,_U_,_V_> (f:FunctionAtoBType<_T_,FunctionAtoBType<_U_,_V_>>) => FunctionAtoBType<_T_,FunctionAtoBType<_U_,_V_>> } */ const curry = f => x => y => f(x,y); /** * Take af function of two arguments in curried style and return a function of two arguments. * @haskell uncurry :: ( a -> b -> c) -> ((a,b) -> c) * @type { <_T_,_U_,_V_> (f:FunctionAtoBType<_T_,FunctionAtoBType<_U_,_V_>>) => FunctionAtoBType<_T_,_U_,_V_> } */ const uncurry = f => (x,y) => f(x)(y); /** * Convert JS boolean to Church boolean * @param { Boolean } value * @return { ChurchBooleanType & Function } */ const toChurchBoolean = value => /** @type { ChurchBooleanType& Function } */ value ? T : F; /** * Convert Church boolean to JS boolean * @param { ChurchBooleanType & Function } churchBoolean * @return { Boolean } */ const toJsBool = churchBoolean => churchBoolean(true)(false);/** * @typedef MaybeMonadType * @template _T_ * @property { <_V_> ((_T_) => MaybeType<_V_>) => MaybeType<_V_> } and * @property { <_V_> ((_T_) => _V_) => MaybeType<_V_> } fmap * @property { <_V_> (_V_) => MaybeType<_T_> } pure * @property { () => MaybeType<_T_> } empty */ const MaybePrototype = () => undefined; MaybePrototype.and = function (bindFn) { let returnVal; this (_ => returnVal = Nothing) (x => returnVal = bindFn(x)); return returnVal; }; MaybePrototype.fmap = function (mapper) { return this.and(x => Just(mapper(x))); }; MaybePrototype.pure = val => Just(val); MaybePrototype.empty = () => Nothing; /** * @typedef { ProducerType<*> } NothingBaseType */ /** * @typedef { NothingBaseType & MaybeMonadType<*> } NothingType */ /** * Nothing is the error case of the Maybe type. A "Maybe a" can be either Nothing or "{@link Just} a". * Nothing is immutable. Nothing is a singleton. * Nothing is used to get around missing null/undefined checks. * @haskell Nothing :: Maybe a * @pure * @type { NothingType } * * @example * const mayFoo = (null == foo) ? Nothing : Just(foo); * mayFoo * (_ => console.error("cannot find foo")) * (x => doSomethingWithFoo(x)); */ const Nothing = Left (undefined); Object.setPrototypeOf(Nothing, MaybePrototype); /** * @typedef { NothingType | JustType<_T_> } MaybeType * @template _T_ * @pure */ /** * Type of the {@link Just} constructor after being bound to a value x of type _T_. * @template _T_ * @typedef { <_U_> (f:*) => (f:FunctionAtoBType<_T_, _U_>) => _U_ } JustBaseType */ /** @typedef { JustBaseType<_T_> & MaybeMonadType } JustType * @template _T_ */ /** * Just is the success case of the Maybe type. A "Maybe a" can be either {@link Nothing} or "Just a". * Just values are immutable. * Just is used to get around missing null/undefined checks. * @haskell Just a :: Maybe a * @pure * @type { <_T_> (x:_T_) => JustType<_T_>} * @example * const mayFoo = (null == foo) ? Nothing : Just(foo); * mayFoo * (_ => console.error("cannot find foo")) * (x => doSomethingWithFoo(x)); */ const Just = val => { const r = Right(val); Object.setPrototypeOf(r, MaybePrototype); return r; }; /** * The catMaybes function takes a list of Maybes and returns a list of all the Just values. * * @template _T_ * @haskell [Maybe a] -> [a] * @param { Iterable> } maybes * @returns { Array<_T_> } */ const catMaybes$1 = maybes => { const result = []; for (const maybe of maybes) { maybe(_ => _)(val => result.push(val)); } return result; }; /** * Chooses between the two given Maybe values. * If the first is a {@link JustType} it will be returned, * otherwise the second value will be returned. * * @type {<_T_> * (maybe1: MaybeType<_T_>) * => (maybe2: MaybeType<_T_>) * => MaybeType<_T_> * } * * @example * const just = Just(1); * const nothing = Nothing; * * const choice1 = choiceMaybe(just)(nothing) * const choice2 = choiceMaybe(nothing)(just) * console.log(choice1 === just && choice2 === just); * // => Logs 'true' */ const choiceMaybe = maybe1 => maybe2 => maybe1 (_ => maybe2) (_ => maybe1);/** * Creates a {@link SequenceType} which contains all given arguments as values. * The argument list might be empty, resulting in an empty iterator. * * @constructor * @pure * @template _T_ * @param { ..._T_ } values * @returns { SequenceType<_T_> } * * @example * const result = Seq(1, 2); * * console.log(...result); * // => Logs '1' '2' */ const Seq = (...values) => { const seqIterator = () => { let index = 0; const next = () => { const result = ( index > values.length -1 ) ? { done: true, value: undefined } : { done: false, value: values[index] }; index++; return result; }; return { next } }; return createMonadicSequence( seqIterator ) };/** * Creates a {@link SequenceType} which contains just the given value. * * @constructor * @pure * @haskell pure :: a -> [a] * @template _T_ * @param { _T_ } value * @returns { SequenceType<_T_> } * * @example * const seq = PureSequence(1); * * console.log(...seq); * // => Logs '1' */ const PureSequence = value => Seq(value);/** * @module stdlib * Kolibri standard library with functions and data structures that are most commonly used. * The stdlib has no dependencies. * It only delegates to other modules where the actual implementation is implemented and tested. */ // to do // Eq typeclass, symmetry, reflexivity // booleanEq, pairEq, tupleEq, eitherEq, choiceEq, maybeEq, arrayEq // functor typeclass, associativity (if pure), left and right identity // pairMap, tupleMap, eitherMap (only Right), choiceMap (n functions), maybeMap // Num? Ord? Monoid? Monad? /** * Log levels can be compared for equality by instance identity. * See also {@link contains},{@link toString},{@link fromString}. * @pure * @immutable * @typedef { PairSelectorType } LogLevelType */ /** * @typedef { LOG_TRACE | LOG_DEBUG | LOG_INFO | LOG_WARN | LOG_ERROR | LOG_FATAL | LOG_NOTHING } LogLevelChoice */ /** * Alias for the use of the {@link Pair} constructor as a {@link LogLevelType}. * @type { PairType } * @private */ const LogLevel = Pair; /** * Getter for the numeric value of a log level. * @private */ const levelNum = fst; /** * Getter for the name of a log level. * @private */ const name$1 = snd; /** * @type { LogLevelType } */ const LOG_TRACE = LogLevel(0)("TRACE"); /** * @type { LogLevelType } */ const LOG_DEBUG = LogLevel(1)("DEBUG"); /** * @type { LogLevelType } */ const LOG_INFO = LogLevel(2)("INFO"); /** * @type { LogLevelType } */ const LOG_WARN = LogLevel(3)("WARN"); /** * @type { LogLevelType } */ const LOG_ERROR = LogLevel(4)("ERROR"); /** * @type { LogLevelType } */ const LOG_FATAL = LogLevel(5)("FATAL"); /** * @type { LogLevelType } */ const LOG_NOTHING = LogLevel(6)("NOTHING"); /** * @type { Array } */ const ALL_LOG_LEVELS = [ LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL, LOG_NOTHING, ]; /** * Whether the logger will log at the current logging level. * @type { (loggingLevel: LogLevelType, loggerLevel: LogLevelType) => Boolean } */ const contains = (loggingLevel, loggerLevel) => loggerLevel(levelNum) >= loggingLevel(levelNum); /** * @type { (logLevel: LogLevelType) => String } */ const toString = logLevel => logLevel(name$1); /** * @type { (str: String) => EitherType } */ const fromString = str => { const level = ALL_LOG_LEVELS.find(logLevel => logLevel(name$1) === str); return level ? Right(level) : Left(`Unknown log level: "${str}"`); };// -- logging level -- /** * This is a singleton state. * The currently active logging level. * Only messages from loggers whose have at least this log level are logged. * Default log level is {@link LOG_INFO}. * @type { IObservable } * @private */ const loggingLevelObs = Observable(LOG_INFO); /** * This function can be used to set the logging level for the logging framework. * Only messages whose have at least the set log level are logged. * @param { LogLevelChoice } newLoggingLevel * @example * setLoggingLevel(LOG_DEBUG); */ const setLoggingLevel = loggingLevelObs.setValue; /** * Getter for the loggingLevel. * @return { LogLevelType } - the currently active logging level */ const getLoggingLevel = loggingLevelObs.getValue; /** * What to do when the logging level changes. * @impure * @type { (cb:ValueChangeCallback) => void } */ const onLoggingLevelChanged = loggingLevelObs.onChange; // -- logging context -- /** * This is a singleton state. * The currently active logging context. * Only loggers whose context have this prefix are logged. * @type { IObservable } * @private */ const loggingContextObs = Observable(""); /** * This function can be used to define a logging context for the logging framework. * Messages will only be logged, if the logger context is more specific than the logging context. * @param { LogContextType } newLoggingContext - the newly set context to log * @example * setLoggingContext("ch.fhnw"); * // logging context is now set to "ch.fhnw" * // loggers with the context "ch.fhnw*" will be logged, all other messages will be ignored. */ const setLoggingContext = loggingContextObs.setValue; // noinspection JSUnusedGlobalSymbols /** * Getter for the logging context. * @return { LogContextType } - the current logging context */ const getLoggingContext = loggingContextObs.getValue; /** * What to do when the logging context changes. * @impure * @type { (cb:ValueChangeCallback) => void } */ const onLoggingContextChanged = loggingContextObs.onChange; // -- logging message formatter -- /** * The formatting function used in this logging environment. * @type { IObservable } * @private */ const globalMessageFormatterObs = Observable(_context => _logLevel => id); /** * This function can be used to specify a global formatting function for log messages. * Appenders are free to use this global function (maybe as a default or as a fallback) * or to use their own, specific formatting functions. * @impure **Warning:** this is global mutable state and can have far-reaching effects on log formatting. * @param { LogMessageFormatterType } formattingFunction * @example * const formatLogMsg = context => logLevel => logMessage => { * const date = new Date().toISOString(); * return `[${logLevel}]\t${date} ${context}: ${logMessage}`; * } * setGlobalMessageFormatter(formatLogMsg); */ const setGlobalMessageFormatter = globalMessageFormatterObs.setValue; /** * Returns the currently used global formatting function. * @impure **Warning:** different values by come at different times. * @type { () => LogMessageFormatterType } */ const getGlobalMessageFormatter = globalMessageFormatterObs.getValue; /** * What to do when the log formatting function changes. * @impure will typically change the side effects of logging. * @type { (cb:ValueChangeCallback) => void } */ const onGlobalMessageFormatterChanged = globalMessageFormatterObs.onChange; // -- logging appender list -- /** * This is a singleton state. * @private * @type { Array } */ const appenders = []; /** * This is a singleton state. * The currently active {@link AppenderType}. * @type { IObservableList } * @private */ const appenderListObs = ObservableList(appenders); /** * @type { () => Array } */ const getAppenderList = () => appenders; /** * Adds one or multiple {@link AppenderType}s to the appender list. * @param { ...AppenderType } newAppender */ const addToAppenderList = (...newAppender) => newAppender.forEach(app => appenderListObs.add(app)); /** * Removes a given {@link AppenderType} from the current appender list. * @impure * @param { AppenderType } appender */ const removeFromAppenderList = appenderListObs.del; /** * @impure * @type { (cb: ConsumerType) => void } */ const onAppenderAdded = appenderListObs.onAdd; /** * @impure * @type { (cb: ConsumerType) => void } */ const onAppenderRemoved = appenderListObs.onDel;/** * Yields a configured log function called "logger". * Processes all log actions, which have a {@link LogLevelType} equals or beneath * the {@link LogLevelType} returned by the function "loggingLevel". * * Furthermore, each log statement has a context, see {@link LogContextType}. * The log message will only be logged, if the loggingContext * (set with {@link setLoggingContext}) is a prefix of the logger context. * * The result of the callback function {@link LogMessageFormatterType} * will be logged using the given {@link AppendCallback}. * * What's the difference between "logger" and "logging" and "log"? * * Every abstraction (level, context, etc.) that starts with "logger" * applies to the _use_ of the log facility in application code. * * Every abstraction (level, context, etc.) the starts with "logging" * applies to the current state or _configuration_ of the log facility that * determines which log statements should currently appear. * * The word "log" is used when the abstraction can be used for both, the logger and the logging * * @function * @pure if the {@link AppendCallback} in the appender list and the parameter msgFormatter of type {@link LogMessageFormatterType} are pure. * @type { * (loggerLevel: LogLevelChoice) * => (loggerContext: LogContextType) * => (msg: LogMeType) * => Boolean * } * @private * @example * const log = logger(LOG_DEBUG)("ch.fhnw"); * log("Andri Wild"); * // logs "Andri Wild" to console */ const logger = loggerLevel => loggerContext => msg => messageShouldBeLogged(loggerLevel)(loggerContext) ? getAppenderList() .map(appender => { const levelName = toString(loggerLevel); const levelCallback = appender[levelName.toLowerCase()]; let success = true; let evaluatedMessage = "Error: cannot evaluate log message: '" + msg + "'!"; try { evaluatedMessage = evaluateMessage(msg); // message eval can fail } catch (e) { success = false; } let formattedMessage = "Error: cannot format log message! '" + evaluatedMessage + "'!"; try { const formatter = appender.getFormatter() // Maybe ( _ => getGlobalMessageFormatter() ) // use global formatter if no specific formatter is set ( id ); // use appender-specific formatter if set formattedMessage = formatter (loggerContext) (levelName) (evaluatedMessage); // formatting can fail } catch (e) { success = false; } // because of evaluation order, a possible eval or formatting error message will be logged // at the current level, context, and appender and will thus be visible. See test case. return levelCallback(formattedMessage) && success; }) .every(id) // all appenders must succeed : false ; /** * Decides if a logger fulfills the conditions to be logged. * @type { (loggerLevel: LogLevelType) => (loggerContext: LogContextType) => Boolean } * @private */ const messageShouldBeLogged = loggerLevel => loggerContext => logLevelActivated(loggerLevel) && contextActivated (loggerContext) ; /** * Returns whether the loggerLevel will log under the current loggingLevel. * @type { (loggerLevel: LogLevelChoice) => Boolean } * @private */ const logLevelActivated = loggerLevel => contains(getLoggingLevel(), loggerLevel); /** * Returns true if the {@link getLoggingContext} is a prefix of the logger context. * @type { (loggerContext: LogContextType) => Boolean } * @private */ const contextActivated = loggerContext => loggerContext.startsWith(getLoggingContext()); /** * if the param "msg" is a function, it's result will be returned. * Otherwise, the parameter itself will be returned. * This allows for both eager and lazy log messages. * @param { !LogMeType } msg - the message to evaluate * @returns { String } the evaluated message * @private */ const evaluateMessage = msg => msg instanceof Function ? msg() : msg; /** * Creates a new logger at log level {@link LOG_TRACE}. * @example * const trace = traceLogger("ch.fhnw")(_context => _level => id); * trace("a message to log to console"); * // writes "a message to log to console" to the console */ const traceLogger = logger(LOG_TRACE); /** * Creates a new logger at log level {@link LOG_DEBUG}. * @example * const debug = debugLogger("ch.fhnw")(_context => _level => id); * debug("a message to log to console"); * // writes "a message to log to console" to the console */ const debugLogger = logger(LOG_DEBUG); /** * Creates a new logger at log level {@link LOG_INFO}. * @example * const debug = infoLogger("ch.fhnw")(_context => _level => id); * debug("a message to log to console"); * // writes "a message to log to console" to the console */ const infoLogger = logger(LOG_INFO); /** * Creates a new logger at log level {@link LOG_WARN}. * @example * const warn = warnLogger("ch.fhnw")(_context => _level => id); * warn("a message to log to console"); * // writes "a message to log to console" to the console */ const warnLogger = logger(LOG_WARN); /** * Creates a new logger at log level {@link LOG_ERROR}. * @example * const error = errorLogger("ch.fhnw")(_context => _level => id); * error("a message to log to console"); * // writes "a message to log to console" to the console */ const errorLogger = logger(LOG_ERROR); /** * Creates a new logger at log level {@link LOG_FATAL}. * @example * const fatal = fatalLogger("ch.fhnw")(_context => _level => id); * fatal("a message to log to console"); * // writes "a message to log to console" to the console */ const fatalLogger = logger(LOG_FATAL);/** * @module logger/loggerFactory * Public convenience API for creating loggers. */ /** * Constructs a logger for each log level using the given context. * @param { LogContextType } context * @returns { LoggerType } * @constructor * @example * const { trace, debug } = LoggerFactory("ch.fhnw"); * trace("Tobias Wyss") // a log message appended on the loglevel {@link LOG_TRACE} * debug("Andri Wild") // a log message appended on the loglevel {@link LOG_DEBUG} */ const LoggerFactory = context => /** @type { LoggerType } */({ trace: traceLogger(context), debug: debugLogger(context), info: infoLogger (context), warn: warnLogger (context), error: errorLogger(context), fatal: fatalLogger(context), });/** * This constant represents a sequence with no values in it. * * @constructor * @pure * @haskell [] * @template _T_ * @type { SequenceType<_T_> } * * @example * const emptySequence = nil; * * console.log(...emptySequence); * // => Logs '' (nothing) */ const nil = Seq();/** * Casts an arbitrary {@link Iterable} into the {@link SequenceType}. * @template _T_ * @param { Iterable<_T_> } iterable * @return { SequenceType<_T_> } */ const toSeq = iterable => map(id)(iterable); /** * Checks whether a given candidate is an {@link Iterable}. * @param { any } candidate * @return { boolean } */ const isIterable = candidate => candidate !== null && candidate !== undefined && candidate[Symbol.iterator] !== undefined; /** * Returns the {@link Iterator} of an {@link Iterable}. * @template _T_ * @param { Iterable<_T_> } iterable * @returns Iterator<_T_> */ const iteratorOf = iterable => iterable[Symbol.iterator](); /** * Checks whether a given candidate is a {@link SequenceType}. * @param { any } candidate * @return { Boolean } true if the candidate is a {@link SequenceType}, false otherwise */ const isSequence = candidate => isIterable(candidate) && Object.getPrototypeOf(candidate) === SequencePrototype; /** * Ensures that the given candidate iterable is a {@link SequenceType}. * @template _T_ * @param { Iterable<_T_> } iterable * @returns { SequenceType<_T_> } the input iterable if it is a {@link SequenceType}, otherwise a new {@link SequenceType} wrapping the input iterable */ const ensureSequence = iterable => isSequence(iterable) ? iterable : toSeq(iterable); /** * A convenience constant that can be used when a Sequence is infinite. * @type { ConsumingPredicateType } */ const forever = _ => true;/** * Transforms each element using the given {@link Functor function}. * * @function * @pure * @haskell (a -> b) -> [a] -> [b] * @typedef MapOperationType * @template _T_ * @template _U_ * @type { * (mapper: Functor<_T_, _U_>) * => SequenceOperation<_T_, _U_> * } * * @example * const numbers = [0, 1, 2]; * const mapped = map(el => el * 2)(numbers); * * console.log(...numbers); * // => Logs '0, 2, 4' */ /** * see {@link MapOperationType} * @template _T_ * @template _U_ * @type {MapOperationType<_T_, _U_>} */ const map = mapper => iterable => { const mapIterator = () => { const inner = iteratorOf(iterable); let mappedValue; const next = () => { const { done, value } = inner.next(); if (!done) mappedValue = mapper(value); return { /**@type boolean */ done, value: mappedValue } }; return { next }; }; return createMonadicSequence(mapIterator); };/** * Monoidal concatenation: flatten an {@link Iterable} of {@link Iterable Iterables} by appending. * @typedef MconcatOperationType * @template _T_ * @function * @pure * @haskell [[a]] -> [a] * @type { * (seqs: Iterable>) * => SequenceType<_T_> * } * * @example * const ranges = map(x => Range(x))(Range(2)); * const result = mconcat(ranges); * * console.log(...result); * // => Logs '0, 0, 1, 0, 1, 2' */ /** * see {@link MconcatOperationType} * @template _T_ * @type { MconcatOperationType<_T_> } */ const mconcat = iterable => { const mconcatIterator = () => { /** * @template _T_ * @type { Iterator<_T_> } */ let current = undefined; const outer = iteratorOf(iterable); const next = () => { while (true) { if (current === undefined) { // if there is no current, get the next sub iterable of the outer iterable const nextOfOuter = outer.next(); if (nextOfOuter.done) return nextOfOuter; current = iteratorOf(nextOfOuter.value); } // grab next value from sub iterable until it is done const nextOfCurrent = current.next(); if (!nextOfCurrent.done) return nextOfCurrent; current = undefined; } }; return { next } }; return createMonadicSequence(mconcatIterator); };/** * Applies the given function to each element of the {@link Iterable} and flats it afterward. * @Note This operation adds a monadic API to the {@link SequenceType}. * * @function * @pure * @haskell (>>=) :: m a -> (a -> m b) -> m b * @template _T_ * @type { * <_U_>(bindFn: (_T_) => Iterable<_U_>) * => (it: Iterable<_T_>) * => SequenceType<_U_> * } * * @example * const numbers = [0, 1, 2, 3]; * const bindFn = el => take(el)(repeat(el)); * const result = bind(bindFn)(numbers); * * console.log(...result); * // => Logs '1, 2, 2, 3, 3, 3' */ const bind = bindFn => it => mconcat( map(bindFn)(it) );/** * The catMaybes function takes an {@link Iterable} of {@link MaybeType Maybes} * and returns an {@link SequenceType} of all the {@link JustXType Just's} values. * * @typedef CatMaybesOperationType * @function * @pure * @haskell [Maybe a] -> a * @template _T_ * @type { (iterable: Iterable>) * => SequenceType<_T_> * } * * @example * const maybes = [Just(5), Just(3), Nothing]; * const result = catMaybes(maybes); * * console.log(...result); * // => Logs '5, 3' */ /** * see {@link CatMaybesOperationType} * @template _T_ * @type { CatMaybesOperationType<_T_> } */ const catMaybes = iterable => { const catMaybesIterator = () => { const inner = iteratorOf(iterable); const next = () => { while (true) { const { value, done } = inner.next(); if (done) return { value: undefined, /** @type Boolean */ done }; const result = catMaybes$1([value]); if (result.length !== 0) { return { value: result[0], done: false }; } } }; return { next }; }; return createMonadicSequence(catMaybesIterator); };/** * Adds the second iterable to the first iterables end. * * @template _T_ * @typedef AppendOperationType * @function * @pure * @haskell [a] -> [a] -> [a] * @type { * (it1: Iterable<_T_>) * => SequenceOperation<_T_> * } * * @example * const numbers = [0, 1, 2]; * const range = Range(2); * const concatenated = append(numbers)(range); * * console.log(...concatenated); * // => Logs '0, 1, 0, 1, 2' */ /** * see {@link AppendOperationType} * @template _T_ * @type {AppendOperationType<_T_>} */ const append = it1 => it2 => mconcat([it1, it2]);/** * Adds the given element to the front of an iterable. * @typedef ConsOperationType * @pure * @haskell (:) :: a -> [a] -> [a] * @template _T_ * @type { (element: _T_) => SequenceOperation<_T_> } * @example * const numbers = [1, 2, 3]; * const element = 0; * const consed = cons(element)(numbers); * * console.log(...consed); * // => Logs '0, 1, 2, 3, 4' */ /** * see {@link ConsOperationType} * @template _T_ * @type { ConsOperationType<_T_> } * */ const cons = element => append( Seq(element) ) ;/** * see {@link CycleOperationType} * @template _T_ * @type { CycleOperationType<_T_> } */ const cycle = iterable => { const cycleIterator = () => { let inner = iteratorOf(iterable); const next = () => { const result = inner.next(); if (!result.done) return result; inner = iteratorOf(iterable); return next(); }; return { next }; }; return createMonadicSequence(cycleIterator); };/** * Discards all elements until the first element does not satisfy the predicate anymore. * * @template _T_ * @typedef DropWhileOperationType * @function * @pure * @haskell (a -> Bool) -> [a] -> [a] * @template _T_ * @type { * (predicate: ConsumingPredicateType<_T_>) * => SequenceOperation<_T_> * } * * @example * const numbers = [0, 1, 2, 3, 4, 5]; * const dropped = dropWhile(el => el < 2)(numbers); * * console.log(...dropped); * // => Logs '2, 3, 4, 5' */ /** * see {@link DropWhileOperationType} * @template _T_ * @type { DropWhileOperationType<_T_> } */ const dropWhile = predicate => iterable => { const dropWhileIterator = () => { const inner = iteratorOf(iterable); const next = () => { let { done, value } = inner.next(); while(!done && predicate(value)) { const n = inner.next(); done = n.done; value = n.value; } return { /** @type boolean */ done, value } }; return { next }; }; return createMonadicSequence(dropWhileIterator); };/** * Jumps over so many elements. * * @template _T_ * @typedef DropOperationType * @function * @pure * @haskell Int -> [a] -> [a] * @type { * (count: number) * => SequenceOperation<_T_> * } * * @example * const numbers = [0, 1, 2, 3]; * const dropped = drop(2)(numbers); * * console.log(...dropped); * // => Logs '2, 3' */ /** * see {@link DropOperationType} * @template _T_ * @type { DropOperationType<_T_> } */ const drop = count => iterable => { const dropIterator = () => { let start = 0; const dropWhileIterable = dropWhile(_ => start++ < count)(iterable); const inner = iteratorOf(dropWhileIterable); return { next : inner.next } }; return createMonadicSequence(dropIterator); };/** * Transforms the given {@link Iterable} by applying each transformer's {@link SequenceOperation} * from begin to end while passing through the intermediate results. * @typedef PipeOperationType * @template _T_ * @type { * (...transformers: SequenceOperation<*,*> ) * => (iterable: Iterable<_T_>) * => (SequenceType<*> | *) * } * @example * const numbers = [0, 1, 2, 3, 4, 5]; * const piped = pipe( * takeWhere(n => n % 2 === 0), * map(n => 2*n), * drop(2) * )(numbers); * * console.log(...piped); * // => Logs '0, 4, 8' */ /** * see {@link PipeOperationType} * @template _T_ * @type { PipeOperationType<_T_> } */ const pipe = (...transformers) => iterable => { iterable = ensureSequence(iterable); for (const transformer of transformers) { iterable = transformer(iterable); } return iterable; };/** * Only keeps elements that satisfy the given predicate. * * @typedef TakeWhereOperationType * @template _T_ * @function * @pure * @haskell (a -> Bool) -> [a] -> [a] * @type { * (predicate: ConsumingPredicateType<_T_>) * => SequenceOperation<_T_> * } * * @example * const numbers = [0, 1, 2, 3, 4, 5]; * * // just keep even numbers * const filtered = takeWhere(el => el % 2 === 0)(it); * * console.log(...filtered); * // => Logs '0, 2, 4' * */ /** * see {@link TakeWhereOperationType} * @template _T_ * @type { TakeWhereOperationType<_T_> } */ const takeWhere = predicate => iterable => { const retainAllIterator = () => { const inner = iteratorOf(iterable); const next = () => { while(true) { const { done, value } = inner.next(); const result = done || predicate(value); if (result) return { /**@type boolean */done, value } ; } }; return { next }; }; return createMonadicSequence(retainAllIterator); };/** * Only keeps elements which does not fulfil the given predicate. * @typedef DropWhereOperationType * @template _T_ * @function * @pure * @haskell (a -> Bool) -> [a] -> [a] * @type { * (predicate: ConsumingPredicateType<_T_>) * => SequenceOperation<_T_> * } * * @example * const numbers = [0, 1, 2, 3, 4, 5]; * * // reject even numbers * const filtered = dropWhere(el => el % 2 === 0)(numbers); * * console.log(...filtered); * // => Logs '1, 3, 5' */ /** * see {@link DropWhereOperationType} * @template _T_ * @type { DropWhereOperationType<_T_> } */ const dropWhere = predicate => iterable => // flip the predicate and call takeWhere takeWhere(el => !predicate(el))(iterable);/** * Processes the iterable backwards. * @typedef ReverseOperationType * @template _T_ * @function * @pure * @haskell [a] -> [a] * @type { * (iterable: Iterable<_T_>) * => SequenceType<_T_> * } * * @example * const numbers = [0, 1, 2]; * const reversed = reverse$(numbers); * * console.log(...reversed); * // => Logs '2, 1, 0' */ /** * see {@link ReverseOperationType} * @template _T_ * @type { ReverseOperationType<_T_> } */ const reverse$ = iterable => { // wrap the code in a function, to keep laziness const reverse$Iterator = () => { const values = [...iterable].reverse(); const valuesIterator = iteratorOf(values); return { next: () => valuesIterator.next() }; }; return createMonadicSequence(reverse$Iterator); };/** * Adds the given element to the end of the {@link Iterable}. * @typedef SnocOperationType * @template _T_ * @function * @pure * @haskell a -> [a] -> [a] * @type { * (element: _T_) * => SequenceOperation<_T_> * } * * @example * const numbers = [0, 1, 2, 3]; * const snocced = snoc(7)(numbers); * * console.log(...snocced); * // => Logs '0, 1, 2, 3, 7' */ /** * see {@link SnocOperationType} * @template _T_ * @type { SnocOperationType<_T_> } */ const snoc = element => iterable => mconcat( Seq( iterable, Seq(element) ));/** * Stop after so many elements. * * @template _T_ * @typedef TakeOperationType * @function * @pure * @haskell Int -> [a] -> [a] * @type { * (count: Number) * => SequenceOperation<_T_> * } * * @example * const numbers = [0,1,2,3]; * const taken = take(2)(numbers); * * console.log(...taken); * // => Logs '0, 1' */ /** * see {@link TakeOperationType} * @template _T_ * @type { TakeOperationType<_T_> } */ const take = count => iterable => { const takeIterator = () => { const inner = iteratorOf(iterable); let start = 0; const next = () => { // the iterator finishes, when the predicate does not return true anymore, // todo dk: copy/paste error? // or the previous iterator has no more elements left const takeDone = start++ >= count; if (takeDone) return { done: true, value: undefined }; return inner.next(); }; return { next }; }; return createMonadicSequence(takeIterator); };/** * Proceeds with the iteration until the predicate becomes true. * @typedef TakeWhileOperationType * @template _T_ * @function * @pure (a -> Bool) -> [a] -> [a] * @type { * (predicate: ConsumingPredicateType<_T_>) * => SequenceOperation<_T_> * } * * @example * const number = [0, 1, 2, 3, 4 ,5]; * * // keep all elements until one element is bigger or equal to 2. * const dropped = takeWhile(el => el <= 2)(numbers); * * console.log(...result); * // => Logs '0, 1, 2' */ /** * see {@link TakeWhileOperationType} * @template _T_ * @type { TakeWhileOperationType<_T_> } */ const takeWhile = predicate => iterable => { const takeWhileIterator = () => { const inner = iteratorOf(iterable); const next = () => { const el = inner.next(); // the iterator finishes, when the predicate does not return true anymore, // or the previous iterator has no more elements left const done = el.done || !predicate(el.value); return { value: el.value, done }; }; return { next }; }; return createMonadicSequence(takeWhileIterator) };/** * {@link zipWith} generalises {@link zip} by zipping with the function given as the first argument, * instead of a Pair constructor. * @typedef ZipWithOperationType * @template _T_ * @function * @pure * @haskell (a -> b -> c) -> [a] -> [b] -> [c] * @type { <_U_, _V_> * (zipper: BiFunction<_T_, _U_, _V_>) * => (it1: Iterable<_T_>) * => (it2: Iterable<_U_>) * => SequenceType<_V_> * } * * @example * const numbers = [0, 1, 2]; * const range = Range(2, 4); * const zipped = zipWith((i,j) => [i,j])(numbers)(range); * console.log(...zipped); * * // => Logs '[0,2], [1,3], [2,4]' */ /** * see {@link ZipWithOperationType} * @template _T_ * @type { ZipWithOperationType<_T_> } */ const zipWith = zipper => it1 => it2 => { /** * @template _V_ * @type _V_ * */ let zippedValue; const zipWithIterator = () => { const inner1 = iteratorOf(it1); const inner2 = iteratorOf(it2); /** * * @template _V_ * @returns { IteratorResult<_V_,_V_> } */ const next = () => { const { done: done1, value: value1 } = inner1.next(); const { done: done2, value: value2 } = inner2.next(); /**@type boolean */ const done = done1 || done2; if (!done) zippedValue = zipper(value1, value2); return { done: done, value: zippedValue }; }; return { next }; }; return createMonadicSequence(zipWithIterator); };/** * Zip takes two {@link Iterable}s and returns an {@link Iterable} of corresponding {@link PairType pairs}. * @typedef ZipOperationType * @template _T_ * @function * @pure * @haskell [a] -> [b] -> [(a, b)] * @type { <_U_> * (it1: Iterable<_T_>) * => (it2: Iterable<_U_>) * => SequenceType> * } * * @example * const numbers = [0, 1, 2], * const range = Range(3, 5); * const zipped = zip(numbers)(range); * * tap(x => console.log(...x))(zipped); * // => Logs '0 3, 1 4, 2 5' */ /** * see {@link ZipOperationType} * @template _T_ * @type { ZipOperationType<_T_> } */ const zip = it1 => it2 => zipWith((i,j) => Pair(i)(j))(it1)(it2);/** * Executes the callback for each element, leaving the original iterable unchanged other than making it a sequence. * @typedef TapOperationType * @template _T_ * @function * @impure the whole point of this function is to execute the callback for each element, producing side effects * @haskell (a -> IO()) -> [a] -> [a] * @type { * (callback: ConsumerType<_T_>) * => (iterable: Iterable<_T_>) * => SequenceOperation<_T_> * } * * @example * const numbers = [0, 1, 2, 3, 4]; * const container = []; * tap(cur => container.push(cur))(numbers); * * console.log(...container); * // => Logs '0, 1, 2, 3, 4' */ /** * see {@link TapOperationType} * @template _T_ * @type {TapOperationType<_T_>} */ const tap = callback => map(x => { callback(x); return x; } );/** * Checks the equality of two non-infinite {@link Iterable iterables}. * * _Note_: Two iterables are considered as equal if they contain or create the exactly same values in the same order. * @see Use ["=="] defined on the {@link SequencePrototype} to perform a comparison in a more readable form. * @typedef EqualOperationType * @function * @pure * @template _T_ * @type { * (it2: Iterable<_T_>) * => Boolean * } * @attention This function only works if at least one iterable is finite as indicated by the name ending with '$'. * * @example * const numbers = [0, 1, 2, 3]; * const range = Range(3); * const result = eq$(numbers)(range); * * console.log(result); * // => Logs 'true' */ /** * see {@link EqualOperationType} * @haskell (==) :: Eq a => a -> a -> Bool * @template _T_ * @param { Iterable<_T_> } it1 * @returns { EqualOperationType<_T_> } */ const eq$ = it1 => it2 => arrayEq([...it1])([...it2]);/** * Performs a reduction on the elements from right to left, using the provided start value and an accumulation function, * and returns the reduced value. * * _Must not be called on infinite sequences!_ * * _Note:_ The callback function takes the accumulator as first argument and the current element as second argument * as conventional in the JavaScript world. This is different from the Haskell world where the order of the arguments * is reversed. * * _Note:_ * Since foldr reduces the {@link Iterable} from right to left, it needs O(n) memory to run the function. * Therefore {@link reduce$} is the better alternative for most cases * * @typedef FoldrOperationType * @template _T_ * @function * @pure * @haskell foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b * @type { <_U_> * (accumulationFn: BiFunction<_U_, _T_, _U_>, start: _U_) * => (iterable: Iterable<_T_>) * => _T_ * } * * @example * const numbers = [0, 1, 2, 3, 4, 5]; * const result = foldr$((acc, cur) => cur + acc, 0)(numbers); * * console.log(result); * // => Logs "15" */ /** * see {@link FoldrOperationType} * @template _T_ * @type { FoldrOperationType<_T_> } */ const foldr$ = (accumulationFn, start) => iterable => { const inner = reverse$(iterable); let accumulator = start; for (const current of inner) { accumulator = accumulationFn(accumulator, current); } return accumulator; };/** * @typedef ForEachSequenceOperationType * @template _T_ * @type { * (callback: ConsumerType<_T_>) * => void * } */ /** * Executes the callback for each element and consumes the sequence. * Use only on **finite** iterables. * @typedef ForEachOperationType * @function * @pure * @haskell (a -> b) -> [a] -> Unit * @template _T_ * @type { * (callback: ConsumerType<_T_>) * => (it: Iterable<_T_>) * => void * } * * @example * const numbers = [0, 1, 2, 3, 4]; * const container = []; * forEach$(cur => container.push(cur))(numbers); * * console.log(...container); * // => Logs '0, 1, 2, 3, 4' */ /** * see {@link ForEachOperationType} * @template _T_ * @type {ForEachOperationType<_T_>} */ const forEach$ = callback => iterable => { for (const current of iterable) { callback(current); } };/** * Return the next value without consuming it. `undefined` when there is no value. * @typedef HeadOperationType * @template _T_ * @function * @pure * @haskell [a] -> a * @type { * (iterable: Iterable<_T_>) * => _T_ | undefined * } * * @example * const numbers = [1, 2, 3, 4]; * const result = head(numbers); * * console.log(result); * // => Logs '1' */ /** * see {@link HeadOperationType} * @template _T_ * @type { HeadOperationType<_T_> } */ const head = iterable => { const inner = iteratorOf(iterable); const { done, value } = inner.next(); return done ? undefined : value; };/** * Returns true, if the iterables head is undefined. * @typedef IsEmptyOperationType * @template _T_ * @function * @pure * @haskell [a] -> Bool * @type { * (iterable: Iterable<_T_>) * => Boolean * } * * @example * const empty = [] * const result = isEmpty(empty); * * console.log(result); * // Logs 'true' */ /** * see {@link IsEmptyOperationType} * @template _T_ * @type { IsEmptyOperationType<_T_> } */ const isEmpty = iterable => iteratorOf(iterable).next().done;const ILLEGAL_ARGUMENT_EMPTY_ITERABLE = "Illegal argument error: iterable must not be empty!";/** * @typedef SafeMaxOperationSequenceType * @template _T_ * @param { BiPredicate<_T_, _T_> } [comparator] - an optional comparing function which returns true if the second argument is larger than the first * @returns MaybeType<_T_> */ /** * Returns the largest element of an {@link Iterable}. * * _Note_: * To determine the largest element, a comparator function is used. * This function compares two elements by default with the `< (LT)` operator, * where on the left side is the current largest element when processing the iterable. * If needed, a different comparator can also be passed as a second argument to {@link safeMax$} * and will then be used to determine the largest element. * * @typedef SafeMaxOperationType * @template _T_ * @function * @pure * @haskell Ord a => [a] -> Maybe a * @param { Iterable<_T_> } iterable - a finite iterable * @param { BiPredicate<_T_, _T_> } [comparator] - an optional comparing function which returns true if the second argument is larger than the first * @returns MaybeType<_T_> * * @example * const numbers = [1, 3, 0, 5]; * const maybeMax = safeMax$(numbers); * * maybeMax * (_ => console.log('iterable was empty, no max!') * (x => console.log(x)); * // => Logs '5' */ /** * see {@link SafeMaxOperationType} * @template _T_ * @type { SafeMaxOperationType<_T_> } */ const safeMax$ = (iterable, comparator = (a, b) => a < b) => { const inner = iteratorOf(iterable); let { value: currentMax, done: isEmpty } = inner.next(); if (isEmpty) { // iterable is empty, no max can be found return Nothing; } while (!isEmpty) { const nextEl = inner.next(); isEmpty = nextEl.done; if (!isEmpty && comparator(currentMax, nextEl.value)) { currentMax = nextEl.value; } } return Just(currentMax); };/** * @typedef MaxOperationSequenceType * @template _T_ * @param { BiPredicate<_T_, _T_> } [comparator] - an optional comparing function which returns true if the second argument is larger than the first * @returns _T_ */ /** * Returns the largest element of a **non-empty** {@link Iterable}. * * _Note_: * To determine the largest element, a comparator function is used. * This function compares two elements by default with the `< (LT)` operator, * where on the left side is the current largest element when processing the iterable. * If needed, a different comparator can also be passed as a second argument to {@link max$} * and will then be used to determine the largest element. * @typedef MaxOperationType * @template _T_ * @function * @pure * @haskell Ord a => [a] -> a * @param { Iterable<_T_> } iterable - a non-empty finite iterable * @param { BiPredicate<_T_, _T_> } [comparator] - an optional comparing function which returns true if the second argument is larger than the first * @returns _T_ * @throws { Error } {@link ILLEGAL_ARGUMENT_EMPTY_ITERABLE} if the given iterable is empty * * @example * const numbers = [1, 3, 0, 5]; * const maximum = max$(numbers); * * console.log(maximum); * // => Logs '5' */ /** * see {@link MaxOperationType} * @template _T_ * @type { MaxOperationType<_T_> } */ const max$ = (iterable, comparator = (a, b) => a < b) => { let returnVal; const maybeResult = safeMax$(iterable, comparator); maybeResult (_ => { throw Error(ILLEGAL_ARGUMENT_EMPTY_ITERABLE) }) (x => returnVal = x); return returnVal; };/** * @typedef MinOperationSequenceType * @template _T_ * @param { BiPredicate<_T_, _T_> } [comparator] - an optional comparing function which returns true if the first argument is smaller than the second * @returns _T_ */ /** * Returns the smallest element of a **non-empty** {@link Iterable}. * * _Note_: * To determine the smallest element, a comparator function is used. * This function compares two elements by default with the `< (LT)` operator. * Where on the left side is the current smallest element when processing the iterable. * If needed, a different comparator can also be passed as a second argument to {@link min$} * and will then be used to determine the smallest element. * * * @typedef MinOperationType * @template _T_ * @function * @pure * @haskell Ord a => [a] -> a * @param { Iterable<_T_> } iterable - a non-empty finite iterable * @param { BiPredicate<_T_, _T_> } [comparator] - an optional comparing function which returns true if the first argument is smaller than the second * @returns _T_ * @throws { Error } {@link ILLEGAL_ARGUMENT_EMPTY_ITERABLE} if the given iterable is empty * * @example * const numbers = [1, 3, 0, 5]; * const minimum = min$(numbers); * * console.log(minimum); * // => Logs '0' */ /** * see {@link MinOperationType} * @template _T_ * @type { MinOperationType<_T_> } */ const min$ = (iterable, comparator = (a, b) => a < b) => max$(iterable, (a,b) => ! comparator(a,b));/** * @typedef SafeMinOperationSequenceType * @template _T_ * @param { BiPredicate<_T_, _T_> } [comparator] - an optional comparing function which returns true if the first argument is smaller than the second * @returns MaybeType<_T_> */ /** * Returns the smallest element of an {@link Iterable}. * * _Note_: * To determine the smallest element, a comparator function is used. * This function compares two elements by default with the `< (LT)` operator, * where on the left side is the current largest element when processing the iterable. * If needed, a different comparator can also be passed as a second argument to {@link safeMin$} * and will then be used to determine the smallest element. * @typedef SafeMinOperationType * @template _T_ * @function * @pure * @haskell Ord a => [a] -> Maybe a * @param { Iterable<_T_> } iterable - a finite iterable * @param { BiPredicate<_T_, _T_> } [comparator] - an optional comparing function which returns true if the first argument is smaller than the second * @returns MaybeType<_T_> * @throws { Error } {@link ILLEGAL_ARGUMENT_EMPTY_ITERABLE} if the given iterable is empty * * @example * const numbers = [0, 1, 2, 3]; * const maybeMin = safeMin$(numbers); * * maybeMin * (_ => console.log('iterable was empty, no min!') * (x => console.log(x)); * // => Logs '0' */ /** * see {@link SafeMinOperationType} * @template _T_ * @type { SafeMinOperationType<_T_> } */ const safeMin$ = (iterable, comparator = (a, b) => a < b) => safeMax$(iterable, (a,b) => ! comparator(a,b));/** * Performs a reduction on the elements, using the provided start value and an accumulation function, and returns the reduced value. * @see foldl$ is an alias for reduce$ * @typedef ReduceSequenceOperationType * @template _T_ * @function * @pure * @haskell foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b * * @type { <_U_> * (accumulationFn: BiFunction<_U_, _T_, _U_>, start: _U_) * => _T_ * } */ /** * Performs a reduction on the elements, using the provided start value and an accumulation function, and returns the reduced value. * @see foldl$ is an alias for reduce$ * * @template _T_ * @function * @pure * @haskell foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b * * @type { <_U_> * (accumulationFn: BiFunction<_U_, _T_, _U_>, start: _U_) * => (iterable: Iterable<_T_>) * => _T_ * } * * @example * const number = [0, 1, 2, 3, 4, 5]; * const res = foldl$((acc, cur) => acc + cur, 0)(numbers); * * console.log(res); * // => Logs "15" */ const reduce$ = (accumulationFn, start) => iterable => { let accumulator = start; for (const current of iterable) { accumulator = accumulationFn(accumulator, current); } return accumulator; }; const foldl$ = reduce$;/** * Transforms the passed {@link Iterable} into a {@link String}. * Elements are passes through the String() constructor, separated by a commas and enclosed in square brackets. * @typedef ShowOperationType * @function * @pure * @haskell Show a => [a] -> String * @param { Number } [maxValues=50] - the maximum amount of elements that should be printed * @returns { String } * * @example * const numbers = [0, 1, 2, 3, 4, 5]; * const text = show(numbers, 3); * * console.log(text); * // => Logs '[0,1,2]' */ /** * see {@link ShowOperationType} * @param { Iterable } iterable * @param { Number } [maxValues=50] - the maximum amount of elements that should be printed * @return { String } */ const show = (iterable, maxValues = 50) => "[" + pipe( take(maxValues), reduce$((acc, cur) => acc === "" ? cur : `${acc},${String(cur)}`, ""), )(iterable) + "]";// noinspection GrazieInspection /** * Removes the first element of this iterable. * The head and the tail of the iterable are returned then * @typedef UnconsSequenceOperationType * @function * @pure * @haskell [a] -> (a, [a]) * @template _T_ * @type { (s: PairSelectorType) => (_T_ | Iterable<_T_>) } - the head and the tail as a pair * * @example * const numbers = [0, 1, 2, 3, 4]; * const [head, tail] = uncons(numbers); * * console.log("head:", head, "tail:", ...tail); * // => Logs 'head: 0 tail: 1 2 3 4' */ /** * see {@link UnconsSequenceOperationType} * @template _T_ * @param { Iterable<_T_> } iterable * @returns { UnconsSequenceOperationType<_T_> } } */ const uncons = iterable => { const inner = iteratorOf(iterable); const { value } = inner.next(); const iterator = () => ({ next: () => inner.next() }); return Pair(value)(createMonadicSequence(iterator)); };/** * Constant for the log context that is used as the basis for all Kolibri-internal logging. * @type { String } */ const LOG_CONTEXT_KOLIBRI_BASE = "ch.fhnw.kolibri"; /** * Constant for the log context that is used for all Kolibri-internal testing. * @type { String } */ const LOG_CONTEXT_KOLIBRI_TEST = LOG_CONTEXT_KOLIBRI_BASE + ".test"; /** * Constant for the log context that logs for all contexts. * @type { String } */ const LOG_CONTEXT_All = "";// noinspection GrazieInspection const LOG_CONTEXT_KOLIBRI_SEQUENCE = LOG_CONTEXT_KOLIBRI_BASE+".sequence"; const log$1 = LoggerFactory(LOG_CONTEXT_KOLIBRI_SEQUENCE); /** * This function object serves as prototype for the {@link SequenceType}. * Singleton object. */ function SequencePrototype () { } // does nothing on purpose /** * * @template _T_ * @param { Iterable<_T_> } iterable * @returns { SequenceType<_T_> } */ function setPrototype (iterable) { Object.setPrototypeOf(iterable, SequencePrototype); return /**@type SequenceType*/ iterable; } /** * Builds an {@link SequenceType} by decorating a given {@link Iterator}. * @template _T_ * @param { () => Iterator<_T_> } iteratorConstructor - a function that returns an {@link Iterator} * @returns { SequenceType<_T_> } */ function createMonadicSequence (iteratorConstructor) { const iterable = { [Symbol.iterator]: iteratorConstructor }; // make a new iterable object return setPrototype(iterable); } // monadic sequence operations ---------------------------------- SequencePrototype.and = function (bindFn) { return bind(bindFn)(this); }; SequencePrototype.fmap = function (mapper) { return map(mapper)(this); }; SequencePrototype.pure = val => PureSequence(val); SequencePrototype.empty = () => nil; // terminal sequence operations ---------------------------------- SequencePrototype.show = function (maxValues = 50) { return show(this, maxValues); }; SequencePrototype.toString = function (maxValues = 50) { if (maxValues !== 50) { log$1.warn("Sequence.toString() with maxValues might lead to type inspection issues. Use show("+ maxValues+") instead."); } return show(this, maxValues); }; SequencePrototype.eq$ = function(that) { if (!isIterable(that)) return false; return eq$(this) /* == */ (that); }; SequencePrototype["=="] = SequencePrototype.eq$; SequencePrototype.foldr$ = function(callback, start) { return foldr$(callback, start)(this); }; SequencePrototype.foldl$ = function(callback, start) { return foldl$(callback, start)(this); }; SequencePrototype.forEach$ = function(callback) { return forEach$(callback)(this); }; SequencePrototype.head = function() { return head(this); }; SequencePrototype.isEmpty = function() { return isEmpty(this); }; SequencePrototype.max$ = function(comparator = (a, b) => a < b) { return max$(this, comparator); }; SequencePrototype.safeMax$ = function(comparator = (a, b) => a < b) { return safeMax$(this, comparator); }; SequencePrototype.min$ = function(comparator = (a, b) => a < b) { return min$(this, comparator); }; SequencePrototype.safeMin$ = function(comparator = (a, b) => a < b) { return safeMin$(this, comparator); }; SequencePrototype.reduce$ = function(callback, start) { return reduce$(callback, start)(this); }; SequencePrototype.uncons = function() { return uncons(this); }; // "semigroup-like" sequence operations ------------------------------------- SequencePrototype.append = function (sequence) { return append(this)(sequence); }; SequencePrototype["++"] = SequencePrototype.append; SequencePrototype.catMaybes = function () { return catMaybes(this); }; SequencePrototype.cons = function (element) { return cons(element)(this); }; SequencePrototype.cycle = function () { return cycle(this); }; SequencePrototype.drop = function (n) { return drop(n)(this); }; SequencePrototype.dropWhere = function (predicate) { return dropWhere(predicate)(this); }; SequencePrototype.dropWhile = function (predicate) { return dropWhile(predicate)(this); }; SequencePrototype.tap = function (callback) { return tap(callback)(this); }; SequencePrototype.map = SequencePrototype.fmap; SequencePrototype.mconcat = function () { return mconcat(this); }; SequencePrototype.pipe = function(...transformers) { return pipe(...transformers)(this); }; SequencePrototype.reverse$ = function () { return reverse$(this); }; SequencePrototype.snoc = function (element) { return snoc(element)(this); }; SequencePrototype.take = function (n) { return take(n)(this); }; SequencePrototype.takeWhere = function (predicate) { return takeWhere(predicate)(this); }; SequencePrototype.takeWhile = function (predicate) { return takeWhile(predicate)(this); }; SequencePrototype.zip = function (iterable) { return zip(this)(iterable); }; SequencePrototype.zipWith = function (zipFn) { return zipWith(zipFn)(this); };/** * @module kolibri.sequence.constructors.unfold * The idea was thankfully provided by Daniel Kröni. */ /** * @typedef StateAndValueType * @template _S_, _T_ * @property { _S_ } state * @property { _T_ } value */ /** * @typedef FromStateToNextStateAndValue * @callback * @pure The whole idea of `unfold` is to allow a pure function at this point. Depends on developer discipline. * @template _S_, _T_ * @param { _S_ } state * @return { StateAndValueType<_S_, _T_> | undefined } - `undefined` if the sequence cannot produce any more values */ /** * Creates a {@link SequenceType} from a callback function that generates the next state and value from the current state. * The sequence is supposed to be exhausted when the callback returns `undefined`. * `unfold` abstracts over the proper state management * in the closure scope of an {@link Iterable}'s iterator function. * This allows the {@link FromStateToNextStateAndValue} callback function to be pure. * @template _T_, _S_ * @param { FromStateToNextStateAndValue<_S_, _T_> } fromStateToNextStateAndValue - callback function to generate the next state and value * @param { _S_ } initialState * @return { SequenceType<_T_> } * @example * const zeroToFour = unfold(0, n => n < 5 ? {state: n + 1, value: n} : undefined); * zeroToFour ['=='] Range(4); */ const unfold = (initialState, fromStateToNextStateAndValue) => { const iterator = () => { let runningState = initialState; const next = () => { const result = fromStateToNextStateAndValue(runningState); if (result === undefined) { return { done: true, value: undefined }; } else { runningState = result.state; return { done: false, value: result.value }; } }; return { next }; }; return createMonadicSequence(iterator); };/** * The `incrementFunction` should change the value (make progress) in a way that the `whileFunction` function can * recognize the end of the sequence. * * Contract: * - `incrementFunction` & `whileFunction` should not refer to any mutable state variable (because of side effect) in * the closure. * * @constructor * @pure if `whileFunction` & `incrementFunction` are pure * @template _T_ * @param { _T_ } start - the first value to be returned by this sequence * @param { (_T_) => Boolean } whileFunction - returns false if the iteration should stop * @param { (_T_) => _T_ } incrementFunction - calculates the next value based on the previous * @returns { SequenceType<_T_> } * * @example * const start = 0; * const whileF = x => x < 3; * const incrementF = x => x + 1; * const sequence = Sequence(start, whileF, incrementF); * * console.log(...sequence); * // => Logs '0, 1, 2' */ const Sequence = (start, whileFunction, incrementFunction) => unfold( start, current => whileFunction(current) ? { state: incrementFunction(current), value: current } : undefined );/** * Constructs a new {@link SequenceType} based on the given tuple. Each iteration returns an element of the tuple. * * @constructor * @pure * @template _T_ * @param { (f:ArrayApplierType<_T_>) => any } tuple * @returns { SequenceType<_T_> } * * @example * const [Triple] = Tuple(3); * const triple = Triple(1)(2)(3); * const tupleSequence = TupleSequence(triple); * * console.log(...tupleSequence); * // => Logs '1, 2, 3' */ const TupleSequence = tuple => { // detect number of elements in tuple using a special selector function const lengthSelector = arr => arr.length; const indexSequence = Sequence(0, i => i !== tuple(lengthSelector), i => i + 1); const tupleIterator = () => { // map over indices and grab corresponding element from tuple const innerIterator = iteratorOf(map(idx => tuple(values => values[idx]))(indexSequence)); return { next : innerIterator.next } }; return createMonadicSequence(tupleIterator); };/** * Returns a {@link SequenceType} that will repeatedly yield the value of `arg` when iterated over. * `repeat` will never be exhausted. * * @constructor * @pure * @haskell repeat :: a -> [a] * @template _T_ * @param { _T_ } arg * @returns { SequenceType<_T_> } * * @example * const ones = repeat(1); * const result = take(3)(ones); * * console.log(...result); * // => Logs '1, 1, 1' */ const repeat = arg => Sequence(arg, forever, _ => arg);/** * `replicate(n)(x)` creates a {@link SequenceType} of length `n` with `x` the value of every element. * * @constructor * @pure * @haskell replicate :: Int -> a -> [a] * @type { <_T_> * (n: Number) * => (value: _T_) * => Iterable<_T_> * } * * @example * const trues = replicate(3)(true); * * console.log(...trues); * // => Logs 'true, true, true' */ const replicate = n => value => take(n)(repeat(value));/** * Creates a range of numbers between two inclusive boundaries, * that implements the JS iteration protocol. * First and second boundaries can be specified in arbitrary order, * step size is always the third parameter. * Consider the examples at the end of the documentation. * * Contract: * - End-value may not be reached exactly, but will never be exceeded. * - Zero step size leads to infinite loops. * - Only values that behave correctly with respect to addition and * size comparison may be passed as arguments. * * @constructor * @pure * @haskell (a, a) -> [a] * @param { !Number } firstBoundary - the first boundary of the range * @param { Number } secondBoundary - optionally the second boundary of the range * @param { Number } step - the size of a step, processed during each iteration * @returns SequenceType * * @example * const range = Range(3); * const [five, three, one] = Range(1, 5, -2); * const [three, four, five] = Range(5, 3); * * console.log(...range); * // => Logs '0, 1, 2, 3' */ const Range = (firstBoundary, secondBoundary = 0, step = 1) => { const stepIsNegative = 0 > step; const [left, right] = normalize(firstBoundary, secondBoundary, stepIsNegative); return Sequence(left, value => !hasReachedEnd(stepIsNegative, value, right), value => value + step); }; /** Walk is an alias for {@link Range} that allows for easier discovery since the name "Range" is also * used within the dom API [https://developer.mozilla.org/en-US/docs/Web/API/Range], which * undermines the auto-import when typing "Range" for the first time in a file. * Just typing "Walk" and using the auto-import will lead to here. */ const Walk = Range; /** * Sorts the two parameter a and b by its magnitude. * @param { Number } a * @param { Number } b * @returns { [Number, Number] } */ const sort = (a, b) => { if (a < b) return [a,b]; else return [b,a]; }; /** * Determines if the end of the range is reached. * @param { Boolean } stepIsNegative - signals, which range boundary condition is active * @param { Number } next * @param { Number } end * @returns { boolean } */ const hasReachedEnd = (stepIsNegative, next, end) => stepIsNegative ? next < end : next > end; /** * Make sure, that the left and right values * are in the proper order according to the given step. * @param { Number } left * @param { Number } right * @param { Boolean } stepIsNegative * @returns { [Number, Number] } */ const normalize = (left, right, stepIsNegative) => { const [min, max] = sort(left, right); let next = min; let end = max; if (stepIsNegative) { next = max; end = min; } return [next, end]; };/** * This {@link JsonMonad} can be used to process JSON data or JS objects in a fluent way. * It is mainly used with {@link JinqType}. * @see https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/ * * @constructor * @template _T_ * @param { Object | Array<_T_> } jsObject * @returns MonadType<_T_> * * @example * const result = * from(JsonMonad(jsObject)) * .select(x => x["id"]) * .result() * .get(); * * console.log(...result); * // => Logs all ids of the passed json * */ const JsonMonad = jsObject => { if (!jsObject[Symbol.iterator]) { jsObject = [jsObject]; } const inner = innerIterable(...jsObject); /** * * @template _T_ * @param { MaybeType> & MaybeMonadType<_T_> } maybeObj * @returns { MonadType<_T_> } * @constructor */ const JsonMonadFactory = maybeObj => { const ensureIterable = value => { const it = Array.isArray(value) ? value: [value]; return innerIterable(...it) }; const fmap = f => { // the result can be turned to nothing as well, therefore and on maybe must be used const result = maybeObj.and(iterator => { const newIt = iterator.and(elem => { const mapped = f(elem); // deep dive into json structure return mapped ? ensureIterable(mapped) : nil; }); return isEmpty(newIt) ? Nothing : Just(newIt); }); // wrap result in json monad again return JsonMonadFactory(result); }; const and = f => { // Map each element of this iterable, that might be in this maybe const result = maybeObj.fmap(iterable => { const maybeIterable = iterable.fmap(elem => { // f :: _T_ -> JsonMonad>> const jsonMonad = f(elem); return jsonMonad.get(); // unwrap the JsonMonad to access the iterable in it. }); /**@type SequenceType */ const catted = /**@type any */catMaybes(maybeIterable); return mconcat(catted) }); return JsonMonadFactory(result); }; const pure = a => JsonMonad(PureSequence(a)); const empty = () => JsonMonadFactory(Nothing); const iterator = () => { let inner; maybeObj (() => inner = nil) (it => inner = it); return iteratorOf(inner); }; return { pure, empty, fmap, and, [Symbol.iterator]: iterator, get: () => maybeObj } }; return JsonMonadFactory(Just(inner)); }; /** * Helper function to create a {@link SequenceType} from varargs. * {@link toSeq } can't be used here, because sub iterables shouldn't be consumed * * @template _T_ * @param { ..._T_ } elements - the elements to iterate on * @returns { SequenceType<*> } */ const innerIterable = (...elements) => { const iterator = () => { const inner = iteratorOf(elements); const next = () => inner.next(); return { next }; }; return createMonadicSequence(iterator); };// typedefs /** * Defines how each Sequence is a {@link MonadType}. * @template _T_ * @typedef SequenceMonadType * @property { <_U_> (bindFn: (_T_) => SequenceType<_U_>) => SequenceType<_U_> } and * - monadic _bind_, * - example: `Seq(1, 2).and(x => Seq(x, -x)) ['=='] Seq(1, -1, 2, -2)` * @property { <_U_> (f: (_T_) => _U_) => SequenceType<_U_> } fmap * - functorial _map_, * - example: `Seq(1, 2).fmap(x => x * 2) ['=='] Seq(2, 4)` * @property { (_T_) => SequenceType<_T_> } pure * - applicative _pure_, * - example: `Seq().pure(1) ['=='] Seq(1)` * @property { () => SequenceType<_T_> } empty * - monoidal _empty_ * - example: `Seq().empty() ['=='] Seq()` * */ /** * Collection of all {@link SequenceOperation}s that are defined on a {@link SequenceType}. * @template _T_ * @typedef SequenceOperationTypes * @property { AppendOperationType<_T_> } append * - Type: {@link AppendOperationType} * - append one sequence to another * - Example: `Seq(1).append(Seq(2,3)) ['=='] (Seq(1, 2,3))` * @property { AppendOperationType<_T_> } ['++'] * - Type: {@link AppendOperationType} * - append one sequence to another, alias for {@link append} * - Example: `Seq(1).append(Seq(2,3)) ['++'] (Seq(1, 2,3))` * @property { CatMaybesOperationType<_T_> } catMaybes * - Type: {@link CatMaybesOperationType} * - Sequence of Maybe values to a Sequence of values * - Example: `Seq(Just(1), Nothing, Just(2)).catMaybes() ['=='] (Seq(1, 2))` * @property { ConsOperationType<_T_> } cons * - Type: {@link ConsOperationType} * - Prefix with a single value * - Example: `Seq(1, 2).cons(0) ['=='] (Seq(0, 1, 2))` * @property { CycleOperationType<_T_> } cycle * - Type: {@link CycleOperationType} * - infinite repetition of the original {@link SequenceType} * - Example: `Seq(1, 2).cycle().take(4) ['=='] (Seq(1, 2, 1, 2))` * @property { DropOperationType<_T_> } drop * - Type: {@link DropOperationType} * - return a {@link SequenceType} without the first n elements * - Example: `Seq(1, 2, 3).drop(2) ['=='] (Seq(3))` * @property { DropWhileOperationType<_T_> } dropWhile * - Type: {@link DropWhileOperationType} * - jump over elements until the predicate is not satisfied anymore * - Example: `Seq(1, 2, 3).dropWhile(x => x < 3) ['=='] (Seq(3))` * @property { DropWhereOperationType<_T_> } dropWhere * - Type: {@link DropWhereOperationType} * - jump over all elements that satisfy the predicate * - Example: `Seq(1, 2, 0).dropWhere(x => x > 1) ['=='] (Seq(1, 0))` * @property { TapOperationType<_T_> } tap * - Type: {@link TapOperationType} * - Executes the callback when tapping into each element, great for debugging and separating side effects. * Leaves the original sequence unchanged. * - example: `Seq(1, 2).tap(x => console.log(x)) ['=='] (Seq(1, 2))` * @property { <_U_> (f: (_T_) => _U_) => SequenceType<_U_> } map * - Type: {@link MapOperationType}, alias for {@link SequenceMonadType.fmap} * - functorial _map_, * - example: `Seq(1, 2).map(x => x * 2) ['=='] Seq(2, 4)` * @property { MconcatOperationType<_T_> } mconcat * - Type: {@link MconcatOperationType} * - monoidal concatenation: flatten an {@link Iterable} of {@link Iterable Iterables} by appending. * - Example: `Seq( Seq(1), Seq(2,3)).mconcat() ['=='] (Seq(1,2,3))` * @property { PipeOperationType<_T_> } pipe * - Type: {@link PipeOperationType} * - Run a series of {@link SequenceOperation}s on a {@link SequenceType} * - example: `Seq(1, 2).pipe(map(x => x * 2), drop(1)) ['=='] (Seq(4))` * @property { ReverseOperationType<_T_> } reverse$ * - Type: {@link ReverseOperationType} * - Processes the iterable backwards, *Works only on finite sequences!*. * - example: `Seq(1, 2, 3).reverse$() ['=='] (Seq(3, 2, 1))` * @property { SnocOperationType<_T_> } snoc * - Type: {@link SnocOperationType} * - Append a single element to the end of the {@link SequenceType} * - example: `Seq(1, 2).snoc(3) ['=='] (Seq(1, 2, 3))` * @property { TakeOperationType<_T_> } take * - Type: {@link TakeOperationType} * - take n elements from a potentially infinite {@link SequenceType} * - example: `Seq(1, 2, 3).take(2) ['=='] (Seq(1,2))` * @property { TakeWhereOperationType<_T_> } takeWhere * - Type: {@link TakeWhereOperationType} * - Only keeps elements that satisfy the given predicate. * - example: `Seq(1, 3, 2).takeWhere(x => x < 3) ['=='] (Seq(1, 2))` * @property { TakeWhileOperationType<_T_> } takeWhile * - Type: {@link TakeWhileOperationType} * - Proceeds until the predicate becomes true. * - example: `Seq(0, 1, 2, 0).takeWhile(x => x < 2) ['=='] (Seq(0, 1))` * @property { ZipOperationType<_T_> } zip * - Type: {@link ZipOperationType} * - Combines two {@link Iterable}s into a single sequence of pairs of elements. * - example: `Seq(1, 2).zip("ab").map(([x, y]) => ""+x+y) ['=='] (Seq("1a", "2b")))` * @property { ZipWithOperationType<_T_> } zipWith * - Type: {@link ZipWithOperationType} * - Combines two {@link Iterable}s into a single sequence of results of the callback function. * - example: `Seq(1, 2).zipWith((x, y) => ""+x+y)("ab") ['=='] (Seq("1a", "2b")))` */ /** * Collection of all terminal operations that are defined on a {@link SequenceType}. * @template _T_ * @typedef SequenceTerminalOperationTypes * @property { EqualOperationType<_T_> } "==" * - Type: {@link EqualOperationType} * - Check for element-wise equality * - **Warning**: This only works on finite sequences * - Example: `Seq(1, 2) ['=='] (Seq(1, 2))` * @property { EqualOperationType<_T_> } eq$ * - Type: {@link EqualOperationType} * - Check for element-wise equality * - **Warning**: This only works on finite sequences as indicated by the name ending with `$` * - Example: `Seq(1, 2).eq$(Seq(1, 2))` * @property { ReduceSequenceOperationType<_T_> } foldl$ * - Type: {@link ReduceSequenceOperationType}, same as `reduce$` * - Combines the elements of a **non-empty** sequence left-to-right using the provided start value and an accumulation function. * - example: `Seq(1, 2, 3).foldl$((acc, cur) => "" + acc + cur, "") === "123"` * @property { FoldrOperationType<_T_> } foldr$ * - Type: {@link FoldrOperationType} * - **Must not be called on infinite sequences!** * - Performs a reduction on the elements from right to left, using the provided start value and an accumulation function. * - example: `Seq(1, 2, 3).foldr$((acc, cur) => "" + acc + cur, "") === "321"` * @property { ForEachSequenceOperationType<_T_> } forEach$ * - Type: {@link ForEachOperationType} * - Executes the callback for each element and consumes the sequence. Returns undefined. * - Use only on **finite** sequences. * - example: `Seq(1, 2).forEach$(x => console.log(x))` * @property { HeadOperationType<_T_> } head * - Type: {@link HeadOperationType} * - Returns the first value or `undefined` if the sequence is empty. * - example: `Seq(1, 2, 3).head() === 1` * @property { IsEmptyOperationType<_T_> } isEmpty * - Type: {@link IsEmptyOperationType} * - Returns true, if there are no elements in the sequence. * - example: `Seq().isEmpty() === true` * @property { MaxOperationSequenceType<_T_> } max$ * - Type: {@link MaxOperationType} * - Returns the largest element of a **non-empty** sequence. * - example: `Seq(1, 3, 0, 5).max$() === 5` * @property { SafeMaxOperationSequenceType<_T_> } safeMax$ * - Type: {@link SafeMaxOperationType} * - Returns {@link Just} the largest element of a sequence or {@link Nothing} otherwise. * - example: `Seq(1, 3, 0, 5).safeMax$() ( _ => console.log(":-(")) ( x => console.log(x)) // logs 5` * @property { MinOperationSequenceType<_T_> } min$ * - Type: {@link MinOperationType} * - Returns the smallest element of a **non-empty** sequence. * - example: `Seq(1, 3, 0, 5).min$() === 0` * @property { SafeMinOperationSequenceType<_T_> } safeMin$ * - Type: {@link SafeMinOperationType} * - Returns {@link Just} the smallest element of a sequence or {@link Nothing} otherwise. * - example: `Seq(1, 3, 0, 5).safeMin$() ( _ => console.log(":-(")) ( x => console.log(x)) // logs 0` * @property { ReduceSequenceOperationType<_T_> } reduce$ * - Type: {@link ReduceSequenceOperationType} * - Combines the elements of a **non-empty** sequence left-to-right using the provided start value and an accumulation function. * - example: `Seq(1, 2, 3).reduce$((acc, cur) => "" + acc + cur, "") === "123"` * @property { ShowOperationType<_T_> } show * - Type: {@link ShowOperationType} * - A string representation of the {@link SequenceType} with optionally a maximum amount of elements * - Example: `Seq(1, 2).show() === "[1,2]"` * @property { ShowOperationType<_T_> } toString * - Type: {@link ShowOperationType}, alias for {@link show} * - Note that providing a maximum amount of elements works but is not advised since it will * cause type warnings because it breaks the contract of the inherited `Object.toString()`. * Use {@link show} instead. * - Example: `Seq(1, 2).toString() === "[1,2]"` * @property { UnconsSequenceOperationType<_T_> } uncons * - Type: {@link UnconsSequenceOperationType} * - Returns the head and the tail of the sequence as a {@link PairType}. * - Example: `show(Seq(1,2,3).uncons()) === "[1,[2,3]]"` */ /** * This type combines the {@link Iterable} with {@link SequenceMonadType}. * Objects of this type can therefore be used in [for...of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) loops, * and further syntactical sugar. * * _Note_: The Kolibri defines many functions of type {@link SequenceOperation} which can be used to * transform the elements of this Sequence. * * @template _T_ * @typedef { * Iterable<_T_> * & SequenceMonadType<_T_> * & SequenceOperationTypes<_T_> * & SequenceTerminalOperationTypes<_T_> * } SequenceType */ /** * @template _T_ * @typedef SequenceBuilderType * @property { InsertIntoBuilder<_T_> } prepend - adds one or more elements to the beginning of the {@link SequenceBuilderType} * @property { InsertIntoBuilder<_T_> } append - adds one or more elements to the end of the {@link SequenceBuilderType} * @property { () => SequenceType<_T_> } build - starts the built phase and returns an {@link SequenceType} which iterates over the added elements */ // callbacks /** * Defines a single operation to decorate an existing {@link SequenceType}. * * @callback SequenceOperation * @template _T_ * @template _U_ * @param { Iterable<_T_>} iterable * @returns { SequenceType<_U_>} */ /** * @callback PipeTransformer * @template _T_, _U_ * @type { SequenceOperation<_T_, _U_> | ((SequenceType) => *)} */ /** * Pipe applies the given {@link SequenceOperation} to the {@link Iterable} it is called on. * @callback Pipe * @param { ...SequenceOperation} operations * @returns { SequenceType } */ /** * @template _T_ * @callback ArrayApplierType * @param { Array<_T_> } arr * @returns any */ /** * Adds multiple elements to this {@link SequenceBuilderType}. * @template _T_ * @callback InsertIntoBuilder * @param { ...(_T_ | Iterable<_T_>) } args * @returns SequenceBuilderType<_T_> *//** * @module projector/projectorUtils * Helper functions for use in projectors. */ /** * Helper function to convert time from string representation into number (minutes since midnight). * If the string cannot be parsed, 00:00 is assumed. * @pure * @param { !String } timeString - format "hh:mm" * @return { Number } */ const timeStringToMinutes = timeString => { if( ! /\d\d:\d\d/.test(timeString)) return 0 ; // if we cannot parse the string to a time, assume 00:00 const [hour, minute] = timeString.split(":").map(Number); return hour * 60 + minute; }; /** * Helper function to convert time from number (minutes since midnight) representation to "hh:mm" string. * @pure * @param { !Number } totalMinutes * @return { String } - format "hh:mm" */ const totalMinutesToTimeString = totalMinutes => { const hour = (totalMinutes / 60) | 0; // div const minute = totalMinutes % 60; return String(hour).padStart(2, "0") + ":" + String(minute).padStart(2, "0"); };/** * Projection function that creates a view for input purposes, binds the information that is available through * the inputController, and returns the generated views. * @typedef { <_T_> * (inputController: !SimpleInputControllerType<_T_>, formCssClassName: !String ) * => [HTMLLabelElement, HTMLInputElement] * } InputProjectionType * @impure since calling the controller functions changes underlying models. The DOM remains unchanged. * @note in the future we might want to depend on a more general controller than SimpleInputControllerType. */ /** * An {@link InputProjectionType} that binds the input on value change. * Depending on the control and how the browser handles it, this might require a user action to confirm the * finalization of the value change like pressing the enter key or leaving the input field. * The signature is the same as for {@link InstantInputProjectionType} but the meaning is different. * @template _T_ * @typedef { InputProjectionType<_T_> } ChangeInputProjectionType */ /** * An {@link InputProjectionType} that binds the input on any change instantly. * Depending on the control and how the browser handles it, this might result in each keystroke in a * text field leading to instant update of the underlying model. * The signature is the same as for {@link ChangeInputProjectionType} but the meaning is different. * @template _T_ * @typedef { InputProjectionType<_T_> } InstantInputProjectionType */ /** * A constructor for an {@link InputProjectionType} that binds the input on any change with a given delay in milliseconds such that * a quick succession of keystrokes is not interpreted as input until there is some quiet time. * Each keystroke triggers the defined timeout. If the timeout is still pending while a key is pressed, * it is reset and starts from the beginning. After the timeout expires, the underlying model is updated. * @typedef { <_T_> (quietTimeMs: !Number) => InputProjectionType<_T_> } DebounceInputProjectionType */ /** * Interface for {@link InputProjectionType}s. * @typedef IInputProjector * @property { ChangeInputProjectionType } projectChangeInput * @property { InstantInputProjectionType } projectInstantInput * @property { DebounceInputProjectionType } projectDebounceInput *//** * @module presentationModel * Implementation of the Presentation Model Pattern with Attributes that can be managed in a ModelWorld. */ /** * @typedef {'value'|'valid'|'editable'|'label'|'name'|'type'} ObservableTypeString * Feel free to extend this type with new unique type strings as needed for your application. */ /** @type ObservableTypeString */ const VALUE = "value"; /** @type ObservableTypeString */ const VALID = "valid"; /** @type ObservableTypeString */ const EDITABLE = "editable"; /** @type ObservableTypeString */ const LABEL = "label"; /** @type ObservableTypeString */ const NAME = "name"; /** @type ObservableTypeString */ const TYPE = "type"; // HTML input types: text, number, checkbox, etc. /** * Convenience function to read the current state of the attribute's VALUE observable for the given attribute. * @template _T_ * @param {AttributeType} attribute * @return _T_ */ const valueOf = attribute => attribute.getObs(VALUE).getValue(); /** * @typedef { Object } PresentationModel */ /** * Creates Presentation Model with Attributes for each attribute name with {@link VALUE} and {@link LABEL} observables. * @param { Array } attributeNames - to be used as keys in the returned {@link PresentationModel}. * @return { PresentationModel } * @constructor * @example * const pm = presentationModelFromAttributeNames(["firstname", "lastname"]); */ const presentationModelFromAttributeNames = attributeNames => { const result = Object.create(null); // make sure that we have no prototype attributeNames.forEach ( attributeName => { const attribute = Attribute(undefined); attribute.getObs(LABEL).setValue(attributeName); // default: use the attribute name as the label result[attributeName] = attribute; }); return /** @type PresentationModel */result; }; /** * @typedef ModelWorldType * @template _T_ * @property { ( getQualifier:function():String, name:ObservableTypeString, observable: IObservable<_T_> ) => void } update - * update the value of the named observableType for all attributes that have the same qualifier. * Add the respective observable if it not yet known. * @property { (qualifier:String, newQualifier:String, observables:Object>) => void} updateQualifier - * handle the change when an attribute changes its qualifier such that all respective * internal indexes need to be updated, their values are updated, and nullish newQualifier leads to removal. * @property { (qualifier:String) => _T_} readQualifierValue */ /** * @private constructs the private, single Model World * @return { ModelWorldType } * @constructor */ const ModelWorld = () => { const data = {}; // key -> array of observables const readQualifierValue = qualifier => { const observables = data[qualifier + "." + VALUE]; if (null == observables) { return undefined; } return observables[0].getValue(); // there are no empty arrays }; // handle the change of a value const update = (getQualifier, name, observable) => { const qualifier = getQualifier(); // lazy get if (null == qualifier) { return; } const key = qualifier + "." + name; // example: "Person.4711.firstname" "VALID" -> "Person.4711.firstname.VALID" const candidates = data[key]; if (null == candidates) { data[key] = [observable]; // nothing to notify return; } let found = false; candidates.forEach ( candidate => { if (candidate === observable) { found = true; } else { candidate.setValue(observable.getValue()); } }); if (! found) { candidates.push(observable); // lazy init: we should have been in the list } }; // handle the change of a qualifier const updateQualifier = (qualifier, newQualifier, observables) => { for (const name in observables) { const observable = observables[name]; if (null != qualifier) { // remove qualifier from old candidates const oldKey = qualifier + "." + name; const oldCandidates = data[oldKey]; const foundIndex = oldCandidates.indexOf(observable); if (foundIndex > -1) { oldCandidates.splice(foundIndex, 1); } if (oldCandidates.length === 0) { // delete empty candidates here delete data[oldKey]; } } if (null != newQualifier){ // add to new candidates const newKey = newQualifier + "." + name; let newCandidates = data[newKey]; if (null == newCandidates) { data[newKey] = []; newCandidates = []; } if (newCandidates.length > 0) { // there are existing observables that's values we need to take over observable.setValue(newCandidates[0].getValue()); } newCandidates.push(observable); } } }; return { update, updateQualifier, readQualifierValue } }; /** * @private single instance, not exported, this is currently a secret of this module */ const modelWorld = ModelWorld(); const readQualifierValue = modelWorld.readQualifierValue; // specific export /** * Convenience constructor of an {@link Attribute} that builds its initial value from already existing qualified values (if any) * instead of overriding possibly existing qualified values with the constructor value. * @constructor * @template _T_ * @param { String } qualifier - mandatory. Nullish values make no sense here since one can use {@link Attribute}. * @return { AttributeType<_T_> } * @impure since it changes the ModelWorld. * @example * const firstNameAttr = QualifiedAttribute("Person.4711.firstname"); // attr is set to existing values, if any. */ const QualifiedAttribute = qualifier => Attribute(readQualifierValue(qualifier), qualifier); /** * @callback Converter * @template _T_ * @param { * } value - the raw value that is to be converted * @return { _T_ } - the converted value * @example * dateAttribute.setConverter( date => date.toISOString() ); // external: Date, internal: String */ /** * @callback Validator * @template _T_ * @param { _T_ } value * @return { Boolean } - whether the given value is considered valid. * @example * dateAttribute.setValidator( date => date > Date.now()); // only future dates are considered valid */ /** * @typedef AttributeType * @template _T_ * @property { (name:ObservableTypeString, initValue:*=null) => IObservable} getObs - returns the {@link IObservable} * for the given name and creates a new one if needed with the optional initValue. * The initValue is of type _T_ for the VALUE observable can be different for others, e.g. the * VALID observable is of type Boolean. * @property { (name:ObservableTypeString) => Boolean } hasObs - true if an {@link Observable} * for the given name has already been created, false otherwise. * @property { (value:*) => void } setConvertedValue - sets the value for the {@link VALUE} observable * after piping the value through the optional converter. The value is not of type _T_ since * the converter might convert any type to _T_. * @property { (converter:!Converter) => void } setConverter - use specialized converter, default is {@link id}, * converters are not allowed to be nullish. * There can only ever be at most one converter on an attribute. * @property { (validator:?Validator) => void } setValidator - use specialized Validator, default is null. * There can only ever be at most one validator on an attribute. * @property { (newQualifier:?String) => void } setQualifier - setting the qualifier can have a wide-ranging impact since * the ModelWorld keeps all attributes with the same qualifier synchronized. Any non-nullish qualifier * adds/keeps the attribute to the ModelWorld, any nullish qualifier removes the attribute from * the ModelWorld. * @property { function(): ?String } getQualifier - the optional qualifier */ /** * Constructor that creates a new attribute with a value and an optional qualifier. * @template _T_ * @param { _T_ } value - the initial value * @param { String? } qualifier - the optional qualifier. If provided and non-nullish it will put the attribute * in the ModelWorld and all existing attributes with the same qualifier will be updated to the initial value. * In case that the automatic update is to be omitted, consider using {@link QualifiedAttribute}. * @return { AttributeType<_T_> } * @constructor * @impure since it changes the ModelWorld in case of a given non-nullish qualifier. * @example * const firstNameAttr = Attribute("Dierk", "Person.4711.firstname"); */ const Attribute = (value, qualifier) => { /** @type {Object.< String, IObservable >} */ const observables = {}; const getQualifier = () => qualifier; const setQualifier = newQualifier => { const oldQualifier = qualifier; // store for use in updateQualifier, since that needs the value to properly unregister qualifier = newQualifier; // since updateQualifier sets the qualifier and calls the attribute back to read it, it must have the new value modelWorld.updateQualifier(oldQualifier, qualifier, observables); }; const hasObs = name => observables.hasOwnProperty(name); const makeObservable = (name, initValue) => { const observable = Observable(initValue); // we might observe more types than just _T_, for example VALID: Boolean // noinspection JSValidateTypes // issue with _T_ as generic parameter for the observed value and other observed types observables[name] = observable; // noinspection JSCheckFunctionSignatures observable.onChange( _ => modelWorld.update(getQualifier, name, observable) ); return observable; }; const getObs = (name, initValue = null) => hasObs(name) ? observables[name] : makeObservable(name, initValue); getObs(VALUE, value); // initialize the value at least let convert = id ; const setConverter = converter => { convert = converter; setConvertedValue(getObs(VALUE).getValue()); }; const setConvertedValue = val => getObs(VALUE).setValue(convert(val)); let validator = undefined; // the current validator in use, might change over time let validateListener = undefined; // the "validate" listener on the attribute, lazily initialized const setValidator = newValidator => { validator = newValidator; if (! validateListener && validator) { validateListener = val => getObs(VALID).setValue(validator ? validator(val) : true); getObs(VALUE).onChange( validateListener ); } }; return { getObs, hasObs, setValidator, setConverter, setConvertedValue, getQualifier, setQualifier } };// noinspection JSUnusedGlobalSymbols /** * Create DOM objects from an HTML string. * @param { String } innerString - The string representation of the inner HTML that will be returned as an HTML collection. * @return { HTMLCollection } * @pure * @example * const [label, input] = dom(` *