/g,""))
.getOrThrow("globals.arrangeBy failed!");
// start preparing the new contents for the indexContent
let newIndexContent = `${helpers.indent(5)}`;
const allKnownElements = CATEGORIES
.map(c=>c[1])
.flatMap(x=>x)
.transform(HashSet.ofIterable);
const missingElements = liRows.keySet().diff(allKnownElements);
if (!missingElements.isEmpty()) {
throw "Missing the following elements: " + missingElements;
}
CATEGORIES.forEach(([name,elements]) => {
newIndexContent += getSectionHeader(name);
const rows = elements.map(elt => liRows.get(elt).getOrThrow("can't find row for " + elt));
newIndexContent += rows.mkString("\n");
newIndexContent += getSectionFooter();
});
// conclude the new contents for the indexContent
newIndexContent += `${helpers.indent(5)}
\n`; // close tsd-index-content
// overwrite globals.html -- first the text before the indexContent,
// then the modified indexContent, then the rest.
writeFileSync(
'apidoc/globals.html',
helpers.linesByIndentStr(beforeIndexContent) +
newIndexContent +
helpers.linesByIndentStr(afterIndexContent));
}
================================================
FILE: scripts/make_doc_extra/helpers.ts
================================================
import { Stream } from "../../src/Stream";
import { Vector } from "../../src/Vector";
import { readFileSync } from "fs";
export function requireNotNull(x:T|null): T {
if (x === null) {
throw "requireNotNull got null!";
}
return x;
}
export function indent(count: number): string {
return "\n" + Stream.continually(()=>"\t")
.take(count).mkString("");
}
export type LineByIndent = {contents:string,indent:number};
export type LinesByIndent = Vector;
export function fileGetLinesByIndent(fname: string): LinesByIndent {
const contents = readFileSync(fname).toString();
return Vector.ofIterable(contents.split("\n"))
.map(l => ({indent: l.length-l.trim().length, contents: l}));
}
/**
* extract a tag from the linesbyindent. You tell me the tag name,
* how to match the start tag, I return to you the rows before, the rows within that
* tag (depth>=tagDepth) and the rows after.
* returns [before, atTag, after]
*/
export function linesByIndentGetTagContents(
lines: LinesByIndent,
tagName: string,
startPredicate: (line:string)=>boolean): [LinesByIndent, LinesByIndent, LinesByIndent] {
// split the parts before & after using string the predicate & indentation.
const [before, atTag] = lines.span(l => !startPredicate(l.contents));
const indentAtTag = atTag.head().getOrThrow().indent;
const [tagContents,after] =
atTag.span(l => (l.indent > indentAtTag) ||
(l.indent <= indentAtTag && l.contents.indexOf("" + tagName) < 0));
if (!after.isEmpty()) {
// i want the closing tag also in 'atTag' but I rejected it in the span,
// so take it from after
return [before, tagContents.append(after.head().getOrThrow()), after.drop(1)];
}
return [before, tagContents, after];
}
export function linesByIndentStr(linesByIndent: LinesByIndent): string {
return linesByIndent.map(l => l.contents).mkString("\n");
}
================================================
FILE: scripts/make_doc_extra/make_doc_extra.ts
================================================
import { groupGlobalsByCategory } from "./globals";
import { putClassStaticMethodsOnTop } from "./classes";
console.log("groupGlobalsByCategory");
groupGlobalsByCategory();
console.log("putClassStaticMethodsOnTop");
putClassStaticMethodsOnTop();
console.log("make extra: done!");
================================================
FILE: scripts/make_doc_extra/make_doc_preprocess.ts
================================================
import { readFileSync, writeFileSync } from "fs";
declare global {
interface String {
startsWith(other:string): boolean
}
}
function makeModule(moduleName:string, filename:string): void {
const contents = readFileSync(filename).toString();
const moduleStart = "export module " + moduleName+ " { ";
const contentsWithModuleHeader = contents.trim().startsWith("/**") ?
// add the module header at the end of the apidoc comment
// so the comment apidoc covers the module
contents.replace(/\*\//, "*/ " + moduleStart) :
// add the module header straight at the top of the file
moduleStart + contents;
// in any case close at the end of the file
writeFileSync(filename, contentsWithModuleHeader + "\n}\n");
}
// list of files for which to trick typedoc
// to think they're external modules
makeModule("Comparison", "src/Comparison.ts");
makeModule("Contract", "src/Contract.ts");
makeModule("Option", "src/Option.ts");
makeModule("Either", "src/Either.ts");
makeModule("LinkedList", "src/LinkedList.ts");
makeModule("Stream", "src/Stream.ts");
makeModule("Function", "src/Function.ts");
makeModule("Predicate", "src/Predicate.ts");
================================================
FILE: scripts/prepublish.sh
================================================
#!/usr/bin/env bash
set -e
node_modules/typescript/bin/tsc -p tsconfig.prepublish.json
node ./node_modules/browserify/bin/cmd.js -s prelude_ts dist/src/index.js -o /tmp/prelude_ts_pre.js
scripts/with_header.sh /tmp/prelude_ts_pre.js > dist/src/prelude_ts.js
node ./node_modules/browserify/bin/cmd.js -s prelude_ts_object_formatters dist/src/ChromeDevToolFormatters.js -o dist/src/chrome_dev_tools_formatters.js
./node_modules/uglify-js/bin/uglifyjs --compress --mangle --output /tmp/prelude_ts_premin.js -- /tmp/prelude_ts_pre.js
scripts/with_header.sh /tmp/prelude_ts_premin.js > dist/src/prelude_ts.min.js
rm /tmp/prelude_ts_pre.js /tmp/prelude_ts_premin.js
================================================
FILE: scripts/with_header.sh
================================================
#!/usr/bin/env bash
set -e
cat < ["li",{},
["span",{"style":"color: rgb(136, 19, 145);"},idx+": "],
["object", {"object":x}]])];
}
class VectorHandler implements ElementHandler {
isElement(object:any): boolean {
return object.hashCode && object.equals &&
object.replace && object.sortOn && object._list;
}
getHeader(object:any): any {
return ["span", {}, "Vector(" + object.length() + ")"];
}
hasBody(elt:any): boolean {
return !elt.isEmpty();
}
getBody = getWithToArrayBody
}
// not going to behave well with infinite streams...
class StreamHandler implements ElementHandler {
isElement(object:any): boolean {
return object.hashCode && object.equals && object.sortBy && object.cycle && object.toVector;
}
getHeader(object:any): any {
// not displaying the length for streams in case
// of infinite streams. the user can expand if needed.
return ["span", {}, "Stream(?)"];
}
hasBody(elt:any): boolean {
return !elt.isEmpty();
}
getBody = getWithToArrayBody
}
class ListHandler implements ElementHandler {
isElement(object:any): boolean {
return object.hashCode && object.equals && object.sortBy && object.toVector;
}
getHeader(object:any): any {
// not displaying the length for streams in case
// of infinite streams. the user can expand if needed.
return ["span", {}, "List(" + object.length() + ")"];
}
hasBody(elt:any): boolean {
return !elt.isEmpty();
}
getBody = getWithToArrayBody
}
class HashSetHandler implements ElementHandler {
isElement(object:any): boolean {
return object.hashCode && object.equals && object.hamt && object.intersect;
}
getHeader(object:any): any {
return ["span", {}, "HashSet(" + object.length() + ")"];
}
hasBody(elt:any): boolean {
return !elt.isEmpty();
}
getBody = getWithToArrayBody
}
class HashMapHandler implements ElementHandler {
isElement(object:any): boolean {
return object.hashCode && object.equals && object.hamt && object.valueIterable;
}
getHeader(object:any): any {
return ["span", {}, "HashMap(" + object.length() + ")"];
}
hasBody(elt:any): boolean {
return !elt.isEmpty();
}
getBody(elt:any): any {
return ["ol",
{"style":olStyle},
...elt.toArray().map((kv:any,idx:number) => {
// using object.create to avoid the __proto__ in the GUI
const obj = Object.create(null);
obj.key = kv[0];
obj.value = kv[1];
return ["li",{},
["span",{"style":"color: rgb(136, 19, 145);"},idx+": "],
["object", {"object":obj}]];
})];
}
}
const handlers = [new VectorHandler(),
new StreamHandler(),
new ListHandler(),
new HashSetHandler(),
new HashMapHandler()];
function getHandler(object: any): ElementHandler|undefined {
return handlers.find(h => h.isElement(object));
}
const formatter = {
header: (object: any, config: any): any => {
const handler = getHandler(object);
return handler ? handler.getHeader(object) : null;
},
hasBody: (object: any, config: any): boolean => {
const handler = getHandler(object);
return handler ? handler.hasBody(object) : false;
},
body: (object: any, config:any): any => {
const handler = getHandler(object);
return handler ? handler.getBody(object) : null;
}
};
if (!(window).devtoolsFormatters) {
(window).devtoolsFormatters = [];
}
(window).devtoolsFormatters.push(formatter);
================================================
FILE: src/Collection.ts
================================================
import { WithEquality, Ordering, ToOrderable } from "./Comparison";
import { Value } from "./Value";
import { Option } from "./Option";
import { HashMap } from "./HashMap";
import { Foldable } from "./Foldable";
export interface Collection extends Value, Iterable, Foldable {
/**
* Get the length of the collection.
*/
length(): number;
/**
* true if the collection is empty, false otherwise.
*/
isEmpty(): boolean;
/**
* Convert to array.
*/
toArray(): Array;
/**
* Call a predicate for each element in the collection,
* build a new collection holding only the elements
* for which the predicate returned true.
*/
filter(fn:(v:T)=>v is U): Collection;
filter(predicate:(v:T)=>boolean): Collection;
/**
* Returns a pair of two collections; the first one
* will only contain the items from this collection for
* which the predicate you give returns true, the second
* will only contain the items from this collection where
* the predicate returns false.
*
* Vector.of(1,2,3,4).partition(x => x%2===0)
* => [Vector.of(2,4), Vector.of(1,3)]
*/
partition(predicate:(v:T)=>v is U): [Collection,Collection>];
partition(predicate:(x:T)=>boolean): [Collection,Collection];
/**
* Returns true if the item is in the collection,
* false otherwise.
*/
contains(v:T&WithEquality): boolean;
/**
* Returns true if the predicate returns true for all the
* elements in the collection.
*/
allMatch(predicate:(v:T)=>v is U): this is Collection;
allMatch(predicate:(v:T)=>boolean): boolean;
/**
* Returns true if there the predicate returns true for any
* element in the collection.
*/
anyMatch(predicate:(v:T)=>boolean): boolean;
/**
* If the collection contains a single element,
* return Some of its value, otherwise return None.
*/
single(): Option;
/**
* Group elements in the collection using a classifier function.
* Elements are then organized in a map. The key is the value of
* the classifier, and in value we get the list of elements
* matching that value.
*
* also see [[Collection.arrangeBy]]
*/
groupBy(classifier: (v:T)=>C&WithEquality): HashMap>;
/**
* Matches each element with a unique key that you extract from it.
* If the same key is present twice, the function will return None.
*
* also see [[Collection.groupBy]]
*/
arrangeBy(getKey: (v:T)=>K&WithEquality): Option>;
/**
* Compare values in the collection and return the smallest element.
* Returns Option.none if the collection is empty.
*
* also see [[Collection.minOn]]
*/
minBy(compare: (v1:T,v2:T)=>Ordering): Option;
/**
* Call the function you give for each value in the collection
* and return the element for which the result was the smallest.
* Returns Option.none if the collection is empty.
*
* Vector.of({name:"Joe", age:12}, {name:"Paula", age:6}).minOn(x=>x.age)
* => Option.of({name:"Paula", age:6})
*
* also see [[Collection.minBy]]
*/
minOn(getNumber: ToOrderable): Option;
/**
* Compare values in the collection and return the largest element.
* Returns Option.none if the collection is empty.
*
* also see [[Collection.maxOn]]
*/
maxBy(compare: (v1:T,v2:T)=>Ordering): Option;
/**
* Call the function you give for each value in the collection
* and return the element for which the result was the largest.
* Returns Option.none if the collection is empty.
*
* Vector.of({name:"Joe", age:12}, {name:"Paula", age:6}).maxOn(x=>x.age)
* => Option.of({name:"Joe", age:12})
*
* also see [[Collection.maxBy]]
*/
maxOn(getSortable: ToOrderable): Option;
/**
* Call the function you give for each element in the collection
* and sum all the numbers, return that sum.
* Will return 0 if the collection is empty.
*
* Vector.of(1,2,3).sumOn(x=>x)
* => 6
*/
sumOn(getNumber: (v:T)=>number): number;
}
================================================
FILE: src/Comparison.ts
================================================
import { Option } from "./Option";
/**
* Sorting function for type T: function
* to convert this type to a type which is natively
* sortable in javascript, that is string, number or boolean.
* `((v:T)=>number) | ((v:T)=>string) | ((v:T)=>boolean`
*/
export type ToOrderable = ((v:T)=>number) | ((v:T)=>string) | ((v:T)=>boolean);
/**
* List of types which provide equality semantics:
* some builtin JS types, for which === provides
* proper semantics, and then types providing HasEquals.
* The reason I use all over the place T&WithEquality
* instead of saying earlier
* in the declaration is: https://stackoverflow.com/a/45903143/516188
*/
export type WithEquality
= string
| number
| boolean
| null
| HasEquals;
/**
* A type with semantic equality relationships
*/
export type HasEquals = {equals(other: any): boolean; hashCode(): number;};
/**
* Type guard for HasEquals: find out for a type with
* semantic equality, whether you should call .equals
* or ===
*/
export function hasEquals(v: WithEquality): v is HasEquals {
// there is a reason why we check only for equals, not for hashCode.
// we want to decide which codepath to take: === or equals/hashcode.
// if there is a equals function then we don't want ===, regardless of
// whether there is a hashCode method or not. If there is a equals
// and not hashCode, we want to go on the equals/hashCode codepath,
// which will blow a little later at runtime if the hashCode is missing.
return ((v).equals !== undefined);
}
/**
* Helper function for your objects so you can compute
* a hashcode. You can pass to this function all the fields
* of your object that should be taken into account for the
* hash, and the function will return a reasonable hash code.
*
* @param fields the fields of your object to take
* into account for the hashcode
*/
export function fieldsHashCode(...fields: any[]): number {
// https://stackoverflow.com/a/113600/516188
// https://stackoverflow.com/a/18066516/516188
let result = 1;
for (const value of fields) {
result = 37*result + getHashCode(value);
}
return result;
}
/**
* Helper function to compute a reasonable hashcode for strings.
*/
export function stringHashCode(str: string): number {
// https://stackoverflow.com/a/7616484/516188
var hash = 0, i, chr;
if (str.length === 0) return hash;
for (i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
/**
* Equality function which tries semantic equality (using .equals())
* if possible, degrades to === if not available, and is also null-safe.
*/
export function areEqual(obj: any|null, obj2: any|null): boolean {
if ((obj === null) != (obj2 === null)) {
return false;
}
if (obj === null || obj2 === null) {
return true;
}
if (hasEquals(obj)) {
return obj.equals(obj2);
}
return obj === obj2;
}
/**
* Hashing function which tries to call hashCode()
* and uses the object itself for numbers, then degrades
* for stringHashCode of the string representation if
* not available.
*/
export function getHashCode(obj: any|null): number {
if (!obj) {
return 0;
}
if (hasEquals(obj)) {
return obj.hashCode();
}
if (typeof obj === 'number') {
// this is the hashcode implementation for numbers from immutablejs
if (obj !== obj || obj === Infinity) {
return 0;
}
let h = obj | 0;
if (h !== obj) {
h ^= obj * 0xffffffff;
}
while (obj > 0xffffffff) {
obj /= 0xffffffff;
h ^= obj;
}
return smi(h);
}
const val = obj+"";
return val.length > STRING_HASH_CACHE_MIN_STRLEN ?
cachedHashString(val) :
stringHashCode(val);
}
function cachedHashString(string: string) {
let hashed = stringHashCache[string];
if (hashed === undefined) {
hashed = stringHashCode(string);
if (STRING_HASH_CACHE_SIZE === STRING_HASH_CACHE_MAX_SIZE) {
STRING_HASH_CACHE_SIZE = 0;
stringHashCache = {};
}
STRING_HASH_CACHE_SIZE++;
stringHashCache[string] = hashed;
}
return hashed;
}
// v8 has an optimization for storing 31-bit signed numbers.
// Values which have either 00 or 11 as the high order bits qualify.
// This function drops the highest order bit in a signed number, maintaining
// the sign bit. (taken from immutablejs)
function smi(i32: number): number {
return ((i32 >>> 1) & 0x40000000) | (i32 & 0xbfffffff);
}
const STRING_HASH_CACHE_MIN_STRLEN = 16;
const STRING_HASH_CACHE_MAX_SIZE = 255;
let STRING_HASH_CACHE_SIZE = 0;
let stringHashCache: {[key:string]:number} = {};
/**
* @hidden
*/
export function hasTrueEquality(val: any): Option {
if (!val) {
return Option.none();
}
if (val.equals) {
return Option.of(true);
}
switch (val.constructor) {
case String:
case Number:
case Boolean:
return Option.of(true);
}
return Option.of(false);
}
/**
* Enumeration used to express ordering relationships.
* it's a const enum, is replaced by integers in the source.
*/
export const enum Ordering {
/**
* Lower Than
*/
LT=-1,
/**
* EQuals
*/
EQ=0,
/**
* Greater Than
*/
GT=1
};
/**
* Typescript doesn't infer typeguards for lambdas; it only sees
* predicates. This type allows you to cast a predicate to a type
* guard in a handy manner.
*
* It comes in handy for discriminated unions with a 'kind' discriminator,
* for instance:
*
* .`filter(>(p => p.kind === "in_board"))`
*
* Also see [[typeGuard]], [[instanceOf]] and [[typeOf]].
*/
export type TypeGuard = (x: T) => x is U;
/**
* Typescript doesn't infer typeguards for lambdas; it only sees
* predicates. This type allows you to cast a predicate to a type
* guard in a handy manner.
*
* It comes in handy for discriminated unions with a 'kind' discriminator,
* for instance:
*
* `.filter(typeGuard(p => p.kind === "in_board", {} as InBoard))`
*
* Normally you'd have to give both type parameters, but you can use
* the type witness parameter as shown in that example to skip
* the first type parameter.
*
* Also see [[typeGuard]], [[instanceOf]] and [[typeOf]].
*/
export function typeGuard(predicate:(x:T)=>boolean,
typeWitness?: U): TypeGuard {
return >predicate;
}
/**
* Curried function returning a type guard telling us if a value
* is of a specific instance.
* Can be used when filtering to filter for the type and at the
* same time change the type of the generics on the container.
*
* Vector.of("bad", new Date('04 Dec 1995 00:12:00 GMT')).filter(instanceOf(Date))
* => Vector.of(new Date('04 Dec 1995 00:12:00 GMT'))
*
* Option.of("test").filter(instanceOf(Date))
* => Option.none()
*
* Option.of(new Date('04 Dec 1995 00:12:00 GMT')).filter(instanceOf(Date))
* => Option.of(new Date('04 Dec 1995 00:12:00 GMT'))
*
* Also see [[typeGuard]] and [[typeOf]].
*/
export function instanceOf(ctor: new(...args: any[]) => T): TypeGuard {
// https://github.com/Microsoft/TypeScript/issues/5101#issuecomment-145693151
return >(x => x instanceof ctor);
}
/**
* Curried function returning a type guard telling us if a value
* is of a specific type.
* Can be used when filtering to filter for the type and at the
* same time change the type of the generics on the container.
*
* Vector.of(1,"a",2,3,"b").filter(typeOf("number"))
* => Vector.of(1,2,3)
*
* Option.of(1).filter(typeOf("string"))
* => Option.none()
*
* Option.of("str").filter(typeOf("string"))
* => Option.of("str")
*
* Also see [[instanceOf]] and [[typeGuard]].
*/
export function typeOf(typ: "string"): TypeGuard;
export function typeOf(typ: "number"): TypeGuard;
export function typeOf(typ: "boolean"): TypeGuard;
export function typeOf(typ: "symbol"): TypeGuard;
export function typeOf(typ: string): TypeGuard {
return ((x:any) => typeof x === typ);
}
================================================
FILE: src/Contract.ts
================================================
import { hasTrueEquality } from "./Comparison";
import { toStringHelper } from "./SeqHelpers";
let preludeTsContractViolationCb = (msg:string):void => { throw new Error(msg); };
/**
* Some programmatic errors are only detectable at runtime
* (for instance trying to setup a HashSet of Option<number[]>: you
* can't reliably compare a number[] therefore you can't compare
* an Option<number[]>.. but we can't detect this error at compile-time
* in typescript). So when we detect them at runtime, prelude-ts throws
* an exception by default.
* This function allows you to change that default action
* (for instance, you could display an error message in the console,
* or log the error)
*
* You can reproduce the issue easily by running for instance:
*
* HashSet.of(Option.of([1]))
* => throws
*/
export function setContractViolationAction(action: (msg:string)=>void) {
preludeTsContractViolationCb = action;
}
/**
* @hidden
*/
export function reportContractViolation(msg: string): void {
preludeTsContractViolationCb(msg);
}
/**
* @hidden
*/
export function contractTrueEquality(context: string, ...vals: Array) {
for (const val of vals) {
if (val) {
if (val.hasTrueEquality && (!val.hasTrueEquality())) {
reportContractViolation(
context + ": element doesn't support true equality: " + toStringHelper(val));
}
if (!hasTrueEquality(val).getOrThrow()) {
reportContractViolation(
context + ": element doesn't support equality: " + toStringHelper(val));
}
// the first element i find is looking good, aborting
return;
}
}
}
================================================
FILE: src/Either.ts
================================================
/**
* The [[Either]] type represents an alternative between two value types.
* A "left" value which is also conceptually tied to a failure,
* or a "right" value which is conceptually tied to success.
*
* The code is organized through the class [[Left]], the class [[Right]],
* and the type alias [[Either]] (Left or Right).
*
* Finally, "static" functions on Option are arranged in the class
* [[EitherStatic]] and are accessed through the global constant Either.
*
* Examples:
*
* Either.right(5);
* Either.left(2);
* Either.right(5).map(x => x*2);
*
* Left has the extra [[Left.getLeft]] method that [[Right]] doesn't have.
* Right has the extra [[Right.get]] method that [[Left]] doesn't have.
*/
import { Value, inspect } from "./Value";
import { Option } from "./Option";
import { LinkedList } from "./LinkedList";
import { Vector } from "./Vector";
import { WithEquality, areEqual,
hasTrueEquality, getHashCode } from "./Comparison";
import { contractTrueEquality} from "./Contract";
/**
* Holds the "static methods" for [[Either]]
*/
export class EitherStatic {
/**
* Constructs an Either containing a left value which you give.
*/
left(val: L): Either {
return new Left(val);
}
/**
* Constructs an Either containing a right value which you give.
*/
right(val: R): Either {
return new Right(val);
}
/**
* Curried type guard for Either
* Sometimes needed also due to https://github.com/Microsoft/TypeScript/issues/20218
*
* Vector.of(Either.right(2), Either.left(1))
* .filter(Either.isLeft)
* .map(o => o.getLeft())
* => Vector.of(1)
*/
isLeft(e: Either): e is Left {
return e.isLeft();
}
/**
* Curried type guard for Either
* Sometimes needed also due to https://github.com/Microsoft/TypeScript/issues/20218
*
* Vector.of(Either.right(2), Either.left(1))
* .filter(Either.isRight)
* .map(o => o.get())
* => Vector.of(2)
*/
isRight(e: Either): e is Right {
return e.isRight();
}
/**
* Turns a list of eithers in an either containing a list of items.
* Useful in many contexts.
*
* Either.sequence(Vector.of(
* Either.right(1),
* Either.right(2)));
* => Either.right(Vector.of(1,2))
*
* But if a single element is Left, everything is discarded:
*
* Either.sequence(Vector.of(
* Either.right(1),
* Either.left(2),
* Either.left(3)));
* => Either.left(2)
*
* Also see [[EitherStatic.traverse]]
*/
sequence(elts:Iterable>): Either> {
return Either.traverse(elts, x=>x);
}
/**
* Takes a list, a function that can transform list elements
* to eithers, then return an either containing a list of
* the transformed elements.
*
* const getUserById: (x:number)=>Either = x => x > 0 ?
* Either.right("user" + x.toString()) : Either.left("invalid id!");
* Either.traverse([4, 3, 2], getUserById);
* => Either.right(Vector.of("user4", "user3", "user2"))
*
* But if a single element results in Left, everything is discarded:
*
* const getUserById: (x:number)=>Either = x => x > 0 ?
* Either.right("user" + x.toString()) : Either.left("invalid id!");
* Either.traverse([4, -3, 2], getUserById);
* => Either.left("invalid id!")
*
* Also see [[EitherStatic.sequence]]
*/
traverse(elts:Iterable, fn: (x:T)=>Either): Either> {
let r = Vector.empty();
const iterator = elts[Symbol.iterator]();
let curItem = iterator.next();
while (!curItem.done) {
const v = fn(curItem.value);
if (v.isLeft()) {
return v;
}
r = r.append(v.get());
curItem = iterator.next();
}
return Either.right>(r);
}
/**
* Turns a list of eithers in an either containing a list of items.
* Compared to [[EitherStatic.sequence]], sequenceAcc 'accumulates'
* the errors, instead of short-circuiting on the first error.
*
* Either.sequenceAcc(Vector.of(
* Either.right(1),
* Either.right(2)));
* => Either.right(Vector.of(1,2))
*
* But if a single element is Left, you get all the lefts:
*
* Either.sequenceAcc(Vector.of(
* Either.right(1),
* Either.left(2),
* Either.left(3)));
* => Either.left(Vector.of(2,3))
*/
sequenceAcc(elts:Iterable>): Either,Vector> {
const [lefts,rights] = Vector.ofIterable(elts).partition(Either.isLeft);
if (lefts.isEmpty()) {
return Either.right,Vector>(rights.map(r => r.getOrThrow()));
}
return Either.left,Vector>(lefts.map(l => l.getLeft()));
}
/**
* Applicative lifting for Either.
* Takes a function which operates on basic values, and turns it
* in a function that operates on eithers of these values ('lifts'
* the function). The 2 is because it works on functions taking two
* parameters.
*
* const lifted = Either.liftA2(
* (x:number,y:number) => x+y, {} as string);
* lifted(
* Either.right(5),
* Either.right(6));
* => Either.right(11)
*
* const lifted = Either.liftA2(
* (x:number,y:number) => x+y, {} as string);
* lifted(
* Either.right(5),
* Either.left("bad"));
* => Either.left("bad")
*
* @param R1 the first right type
* @param R2 the second right type
* @param L the left type
* @param V the new right type as returned by the combining function.
*/
liftA2(fn:(v1:R1,v2:R2)=>V, leftWitness?: L) : (p1:Either, p2:Either) => Either {
return (p1,p2) => p1.flatMap(a1 => p2.map(a2 => fn(a1,a2)));
}
/**
* Applicative lifting for Either. 'p' stands for 'properties'.
*
* Takes a function which operates on a simple JS object, and turns it
* in a function that operates on the same JS object type except which each field
* wrapped in an Either ('lifts' the function).
* It's an alternative to [[EitherStatic.liftA2]] when the number of parameters
* is not two.
*
* const fn = (x:{a:number,b:number,c:number}) => x.a+x.b+x.c;
* const lifted = Either.liftAp(fn, {} as number);
* lifted({
* a: Either.right(5),
* b: Either.right(6),
* c: Either.right(3)});
* => Either.right(14)
*
* const lifted = Either.liftAp(
* x => x.a+x.b);
* lifted({
* a: Either.right(5),
* b: Either.left(2)});
* => Either.left(2)
*
* @param L the left type
* @param A the object property type specifying the parameters for your function
* @param B the type returned by your function, returned wrapped in an either by liftAp.
*/
liftAp(fn:(x:A)=>B, leftWitness?: L): (x: {[K in keyof A]: Either;}) => Either {
return x => {
const copy:A = {};
for (let p in x) {
if (x[p].isLeft()) {
return >x[p];
}
copy[p] = x[p].getOrThrow();
}
return Either.right(fn(copy));
}
}
/**
* Applicative lifting for Either. 'p' stands for 'properties'.
* Compared to [[EitherStatic.liftAp]], liftApAcc 'accumulates'
* the errors, instead of short-circuiting on the first error.
*
* Takes a function which operates on a simple JS object, and turns it
* in a function that operates on the same JS object type except which each field
* wrapped in an Either ('lifts' the function).
* It's an alternative to [[EitherStatic.liftA2]] when the number of parameters
* is not two.
*
* const fn = (x:{a:number,b:number,c:number}) => x.a+x.b+x.c;
* const lifted = Either.liftApAcc(fn, {} as number);
* lifted({
* a: Either.right(5),
* b: Either.right(6),
* c:Either.right(3)});
* => Either.right(14)
*
* const fn = (x:{a:number,b:number,c:number}) => x.a+x.b+x.c;
* const lifted = Either.liftApAcc(fn, {} as number);
* lifted({
* a: Either.right(5),
* b: Either.left(2),
* c: Either.left(6)});
* => Either.left(Vector.of(2, 6))
*
* @param L the left type
* @param A the object property type specifying the parameters for your function
* @param B the type returned by your function, returned wrapped in an either by liftAp.
*/
liftApAcc(fn:(x:A)=>B, leftWitness?: L): (x: {[K in keyof A]: Either;}) => Either,B> {
const leftErrs: L[] = [];
return x => {
const copy:A = {};
for (let p in x) {
const field = x[p];
if (field.isLeft()) {
leftErrs.push(field.getLeft());
} else {
copy[p] = x[p].getOrThrow();
}
}
if (leftErrs.length === 0) {
return Either.right,B>(fn(copy));
} else {
return Either.left,B>(Vector.ofIterable(leftErrs));
}
}
}
/**
* Take a partial function (may return undefined or throw),
* and lift it to return an [[Either]] instead.
*
* Note that unlike the [[OptionStatic.lift]] version, if
* the function returns undefined, the Either.lift version will throw
* (the Option.lift version returns None()): if you want to do
* pure side-effects which may throw, you're better off just using
* javascript try blocks.
*
* When using typescript, to help the compiler infer the left type,
* you can either pass a second parameter like `{} as `, or
* call with `lift(...)`.
*
* const add = Either.lift((x:number,y:number) => x+y, {} as string);
* add(1,2);
* => Either.right(3)
*
* const undef = Either.lift((x:number,y:number,z:number) => undefined);
* undef(1,2,3);
* => throws
*
* const throws = Either.lift(() => {throw "x"});
* throws();
* => Either.left("x")
*/
lift(fn: (...args: T)=>U, witness?: L): (...args:T)=>Either {
return (...args:T) => {
try {
const r = fn(...args);
if (r !== undefined) {
return Either.right(r);
}
} catch (err) {
return Either.left(err);
}
throw new Error("liftEither got undefined!");
};
}
/**
* Take a no-parameter partial function (may return undefined or throw),
* call it, and return an [[Either]] instead.
*
* Note that unlike the [[OptionStatic.try_]] version, if
* the function returns undefined, this function will throw
* (the Option.try_ version returns None()): if you want to do
* pure side-effects which may throw, you're better off just using
* javascript try blocks.
*
* When using typescript, to help the compiler infer the left type,
* you can either pass a second parameter like `{} as `, or
* call with `try_(...)`.
*
* Either.try_(Math.random, {} as string);
* => Either.right(0.49884723907769635)
*
* Either.try_(() => undefined);
* => throws
*
* Either.try_(() => {throw "x"});
* => Either.left("x")
*
* Also see [[EitherStatic.lift]], [[OptionStatic.try_]],
* [[OptionStatic.tryNullable]]
*/
try_(fn:()=>T, witness?: L): Either {
return Either.lift<[],L,T>(fn)();
}
}
/**
* The Either constant allows to call the either "static" methods
*/
export const Either = new EitherStatic();
/**
* Either represents an alternative between two value types.
* A "left" value which is also conceptually tied to a failure,
* or a "right" value which is conceptually tied to success.
* "static methods" available through [[EitherStatic]]
*/
export type Either = Left | Right;
/**
* Represents an [[Either]] containing a left value,
* conceptually tied to a failure.
* "static methods" available through [[EitherStatic]]
* @param L the "left" item type 'failure'
* @param R the "right" item type 'success'
*/
export class Left implements Value {
constructor(private value: L) {}
/**
* @hidden
*/
readonly className: "Left" = undefined; // https://stackoverflow.com/a/47841595/516188
/**
* Returns true since this is a Left
*/
isLeft(): this is Left {
return true;
}
/**
* Returns false since this is a Left
*/
isRight(): this is Right {
return false;
}
/**
* Returns true if this is either is a right and contains the value you give.
*/
contains(val: R&WithEquality): boolean {
return false;
}
/**
* If this either is a right, applies the function you give
* to its contents and build a new right either, otherwise return this.
*/
map(fn: (x:R)=>U): Either {
return this;
}
/**
* If this either is a right, call the function you give with
* the contents, and return what the function returns, else
* returns this.
* This is the monadic bind.
*/
flatMap(fn: (x:R)=>Either): Either {
return this;
}
/**
* If this either is a left, call the function you give with
* the left value and return a new either left with the result
* of the function, else return this.
*/
mapLeft(fn: (x:L)=>U): Either {
return new Left(fn(this.value));
}
/**
* Map the either: you give a function to apply to the value,
* a function in case it's a left, a function in case it's a right.
*/
bimap(fnL: (x:L)=>S,fnR: (x:R)=>T): Either {
return new Left(fnL(this.value));
}
/**
* "filter" the either. If it was a Left, it stays a Left.
* If it was a Right and the predicate you pass returns
* true for its value, return the either unchanged.
* But if it was a left and the predicate returns false,
* return a Left with the value returned by the function
* passed as second parameter.
*
* Either.right(-3)
* .filter(x => x >= 0, v => "got negative value: " + v);
* => Either.left("got negative value: -3")
*/
filter(p: (x:R)=>boolean, filterVal: (x:R)=>L): Either {
return this;
}
/**
* Combines two eithers. If this either is a right, returns it.
* If it's a left, returns the other one.
*/
orElse(other: Either): Either {
return other;
}
/**
* Has no effect if this Either is a right. If it's a left however,
* the function you give will be called, receiving as parameter
* the left contents, and an Either equivalent to the one your
* function returns will be returned.
*/
recoverWith(recoveryFn: (left:L)=>Either): Either {
return recoveryFn(this.value);
}
/**
* Execute a side-effecting function if the either
* is a right; returns the either.
*/
ifRight(fn: (x:R)=>void): Either {
return this;
}
/**
* Execute a side-effecting function if the either
* is a left; returns the either.
*/
ifLeft(fn: (x:L)=>void): Either {
fn(this.value);
return this;
}
/**
* Handle both branches of the either and return a value
* (can also be used for side-effects).
* This is the catamorphism for either.
*
* Either.right(5).match({
* Left: x => "left " + x,
* Right: x => "right " + x
* });
* => "right 5"
*/
match(cases: {Left: (v:L)=>U, Right: (v:R)=>U}): U {
return cases.Left(this.value);
}
/**
* If this either is a right, return its value, else throw
* an exception.
* You can optionally pass a message that'll be used as the
* exception message, or an Error object.
*/
getOrThrow(errorInfo?: Error|string): R {
if (typeof errorInfo === 'string') {
throw new Error(errorInfo || "Left.getOrThrow called!");
}
throw errorInfo || new Error("Left.getOrThrow called!");
}
/**
* If this either is a right, return its value, else return
* the value you give.
*/
getOrElse(other: R): R {
return other;
}
/**
* Get the value contained in this left.
* NOTE: we know it's there, since this method
* belongs to Left, not Either.
*/
getLeft(): L {
return this.value;
}
/**
* If this either is a left, return its value, else throw
* an exception.
* You can optionally pass a message that'll be used as the
* exception message.
*/
getLeftOrThrow(message?: string): L {
return this.value;
}
/**
* If this either is a left, return its value, else return
* the value you give.
*/
getLeftOrElse(other: L): L {
return this.value;
}
/**
* Convert this either to an option, conceptually dropping
* the left (failing) value.
*/
toOption(): Option {
return Option.none();
}
/**
* Convert to a vector. If it's a left, it's the empty
* vector, if it's a right, it's a one-element vector with
* the contents of the either.
*/
toVector(): Vector {
return Vector.empty();
}
/**
* Convert to a list. If it's a left, it's the empty
* list, if it's a right, it's a one-element list with
* the contents of the either.
*/
toLinkedList(): LinkedList {
return LinkedList.empty();
}
/**
* Transform this value to another value type.
* Enables fluent-style programming by chaining calls.
*/
transform(converter:(x:Either)=>U): U {
return converter(this);
}
hasTrueEquality(): boolean {
return (this.value && (this.value).hasTrueEquality) ?
(this.value).hasTrueEquality() :
hasTrueEquality(this.value);
}
/**
* Get a number for that object. Two different values
* may get the same number, but one value must always get
* the same number. The formula can impact performance.
*/
hashCode(): number {
return getHashCode(this.value);
}
/**
* Two objects are equal if they represent the same value,
* regardless of whether they are the same object physically
* in memory.
*/
equals(other: Either): boolean {
if (other === this) {
return true;
}
if ((!other) || (!other.isRight) || other.isRight()) {
return false;
}
const leftOther = >other;
contractTrueEquality("Either.equals", this, leftOther);
return areEqual(this.value, leftOther.value);
}
/**
* Get a human-friendly string representation of that value.
*/
toString(): string {
return "Left(" + this.value + ")";
}
/**
* Used by the node REPL to display values.
*/
[inspect](): string {
return this.toString();
}
}
/**
* Represents an [[Either]] containing a success value,
* conceptually tied to a success.
* "static methods" available through [[EitherStatic]]
* @param L the "left" item type 'failure'
* @param R the "right" item type 'success'
*/
export class Right implements Value {
constructor(private value: R) {}
/**
* @hidden
*/
readonly className: "Right" = undefined; // https://stackoverflow.com/a/47841595/516188
/**
* Returns false since this is a Right
*/
isLeft(): this is Left {
return false;
}
/**
* Returns true since this is a Right
*/
isRight(): this is Right {
return true;
}
/**
* Returns true if this is either is a right and contains the value you give.
*/
contains(val: R&WithEquality): boolean {
return areEqual(this.value, val);
}
/**
* If this either is a right, applies the function you give
* to its contents and build a new right either, otherwise return this.
*/
map(fn: (x:R)=>U): Either {
return new Right(fn(this.value));
}
/**
* If this either is a right, call the function you give with
* the contents, and return what the function returns, else
* returns this.
* This is the monadic bind.
*/
flatMap(fn: (x:R)=>Either): Either {
return fn(this.value);
}
/**
* If this either is a left, call the function you give with
* the left value and return a new either left with the result
* of the function, else return this.
*/
mapLeft(fn: (x:L)=>U): Either {
return this;
}
/**
* Map the either: you give a function to apply to the value,
* a function in case it's a left, a function in case it's a right.
*/
bimap(fnL: (x:L)=>S,fnR: (x:R)=>T): Either