type FunType = { (inputValue: input): output; next: (nextFunction: FunType) => FunType; repeat: (this: FunType, count: number) => FunType; repeatUntil: (this: FunType, condition: FunType) => FunType; } const Fun = (inputLamba: (_: input) => output): FunType => { const func = inputLamba as FunType; func.next = function(nextFunction: FunType): FunType { return Fun(inputValue => nextFunction(inputLamba(inputValue))); } func.repeat = function(this: FunType, count: number): FunType { if (count == 0) { return Fun((x: input) => x); } return this.next(this.repeat(count - 1)); } func.repeatUntil = function(this: FunType, condition: FunType): FunType { return Fun((x: input) => { if (condition(x)) { return x; } console.log(x); return this.next(this.repeatUntil(condition))(x); }) // Shorthand // return Fun((x: input) => condition(x) ? x : this.next(this.repeatUntil(condition))(x)); } return func; } // // Some FunType lamdas // const isPositive: FunType = Fun((x: number) => x > 0); const isEven: FunType = Fun((x: number) => x % 2 == 0); const incr: FunType = Fun((x: number) => x + 1); const double: FunType = Fun((x: number) => x * 2); const decr: FunType = Fun((x: number) => x - 1); const convert: FunType = Fun((x: number) => String(x)); const exclaim: FunType = Fun((x: string) => { let y = x + "!"; return y; }); const question: FunType = Fun((x: string) => { let y = x + "?"; return y; }); const saveSqrt = Fun((a: number) => a > 0 ? Math.sqrt(a) : Math.abs(Math.sqrt(a))); let xxx = incr.next(convert) (4) // // Generic id functions // // Id functions do basically nothing but initialize a base where we can work from (initializing a pipeline for example) // Eg. Id functions are the base of all other functions const id: () => FunType = () => Fun(x => x); // function id(): FunType { // return Fun(_ => _); // } // Example usage 1 let exampleId = id().next(incr).next(incr)(0) // -> result would be 2 // | // Define type here // Id functions but for a specific type (Non generic) type Unit = {}; const idNum: FunType = Fun(_ => 0); const idString: FunType = Fun(_ => ""); // | // Define type here // Example usage 2 let exampleId2 = idNum.next(incr).next(incr)(0) // // Countainers / Functors // // A functor is a container that is mappable // Aka: We can run map functions on the containers itself to perform some kind of data update. type Countainer = { content: content; // Content counter: number; // For Example: amount of times object has been changed or mapped. } // Increments the value of counter with 1 function incrCountainer(countainer: Countainer): Countainer { return { content: countainer.content, counter: countainer.counter + 1 }; } // Applies transformer lamda to content of countainer to potentionally map value to other type: content -> newContent function mapCountainerNoFun(countainer: Countainer, transformer: FunType): Countainer { return { counter: countainer.counter + 1, content: transformer(countainer.content) } } // Applies transformer lamda to each element content of array to potentionally map value to other type: content -> newContent function mapArray(transformer: FunType): FunType, Array> { return Fun((array: Array) => array.map(transformer)); } // Applies transformer lamda to content of countainer to potentionally map value to other type: content -> newContent // !! But is wrapped in the FunType so we can use this in a pipeline !! function mapCountainer(transformer: FunType): FunType, Countainer> { return Fun(c => { return { counter: c.counter + 1, content: transformer(c.content) } }) } type Message = { username: string; likes: number; message: string; } // Some pipeline functions for Message type const setUsername = (newUsername: string) => Fun(c => ({ username: newUsername, likes: c.likes, message: c.message })); const incrLikes = () => Fun(c => ({ username: c.username, likes: c.likes + 1, message: c.message })); // We could also write these functions like this function setMessage (msg: string): FunType { return Fun(c => { return { username: c.username, likes: c.likes, message: msg } }) } // Countainer where content is of Message Type let messageCountainer: Countainer = { counter: 0, content: { username: "", likes: 0, message: "" } } // Pipeline usage example with countainer and message // Using mapCountainer const messagePipeline1 = mapCountainer( setUsername("Test") .next(setMessage("This is a message")) .next(incrLikes()) .next(incrLikes()) ) const messagePipeline2 = mapCountainer( setUsername("Test2") .next(setMessage("This is a new and more improved message message")) .next(incrLikes()) .next(incrLikes()) .next(incrLikes()) ) const message1 = messagePipeline1(messageCountainer); const message2 = messagePipeline2(messageCountainer); //console.log(message1); // Result: { // counter: 1, // content: { username: 'Test', likes: 2, message: 'This is a message' } // } // This function will increase all the likes by 1 in a array of Countainers where its type is Message // We use the MapArray function for this operation const increaseAllLikes: FunType>, Array>> = mapArray(mapCountainer(incrLikes())); // Create array const messages: Array> = [message1, message2]; // console.log(increaseAllLikes(messages)); // Result: [ // { // counter: 2, // content: { username: 'Test', likes: 3, message: 'This is a message' } // }, // { // counter: 2, // content: { // username: 'Test2', // likes: 4, // message: 'This is a new and more improved message message' // } // } // ] // Some more examples of array mapping // Basic increment // console.log( // mapArray(incr)([1, 2, 3, 4]) //) // Result: [ 2, 3, 4, 5 ] // Increment, double and decrement in pipeline // console.log( // mapArray(incr.next(double).next(decr))([1, 2, 3, 4]) // ) // Result: [ 3, 5, 7, 9 ] // Increment, check if even. // Here we also perform a mapping function that will change the type of content // console.log( // mapArray(incr.next(isEven))([1, 2, 3, 4]) // ) // Result: [ true, false, true, false ] // // Monoid / Monoidal structure / Monoids // // Monoids are datatypes (structures) that supports a given mathematical operation plus some given values // Examples: // + (Operator in numerical context) // 1 + 2 == 2 + 1 // (a + b) * c == c * (b + a) // a + 0 == 0 + a == a // * (Operator in numerical context) // (a * b) * c == a * (b * c) // a * 1 == 1 * a == a // + (Operator in string context) // (a + b) + c == a + (b + c) // a + "" == "" + a == a // Practical examples with arrays const numArray1: Array = [1, 2, 3]; const numArray2: Array = [2, 3, 4]; const numArray3: Array = [5, 6, 7]; const numArray4: Array = []; const resultNumArr1 = numArray1.concat(numArray2.concat(numArray3)); // -> [1, 2, 3, 2, 3, 4, 5, 6, 7] const resultNumArr2 = numArray1.concat(numArray2).concat(numArray3); // -> [1, 2, 3, 2, 3, 4, 5, 6, 7] const resultNumArr3 = (numArray1.concat(numArray2)).concat(numArray3); // -> [1, 2, 3, 2, 3, 4, 5, 6, 7] // Therefore: resultNumArr1 == resultNumArr2 == resultNumArr31 const resultNumArr4 = numArray4.concat(numArray1); // -> [1, 2, 3] const resultNumArr5 = numArray1.concat(numArray4); // -> [1, 2, 3] // Therefore: resultNumArr4 == resultNumArr5 // This type of reasoning also applies to (monoidal) functions!! // Here monoids come into play let monoidTest1 = id().next(incr).next(incr).next(incr) (0); let monoidTest2 = id().next(incr.next(incr).next(incr)) (0); let monoidTest3 = id().next(incr.next(incr)).next(incr) (0); // Therefore: monoidTest1 == monoidTest2 == monoidTest3 let monoidTest4 = incr.next(id()) (0); let monoidTest5 = id().next(incr) (0); // Therefore: monoidTest4 == monoidTest5 // A monoido or triple is // A type T // An operator + : [T, T] => T // An element z : T // // Such that: // (a + b) + c == a + (b + c) <--- associative law // a + z == z + a == a <--- identity law // // Monads are different than monoids! // Monads (NOT Monoid / Monodial, etc) are Functors and Monoids combined! // // // Pairs // type Pair = { a: a, b: b }; const pairA = (): FunType, a> => Fun(c => c.a); const pairB = (): FunType, b> => Fun(c => c.b); const mapPair = (f1: FunType, f2: FunType): FunType, Pair> => Fun(c => { return { a: f1(c.a), b: f2(c.b), } }) // // Options // type Option = ({ kind: "empty" } | { kind: "full", content: a }) & { then (cont: (_: a) => Option): Option }; const emptyOption = (): Option => ({ kind: "empty", then: function(this: Option, cont: (_: a) => Option) { return bindOption(this, Fun(cont)) } }); const fullOption = (input: a): Option => ({ kind: "full", content: input, then: function(this: Option, cont: (_: a) => Option) { return bindOption(this, Fun(cont)) } }); class OptionFunctors { static join = (nestedOption: Option>): Option => { if (nestedOption.kind == "empty") return emptyOption(); if (nestedOption.content.kind == "empty") return emptyOption(); return fullOption(nestedOption.content.content); } static funJoin = (): FunType>, Option> => { return Fun((nestedOption: Option>) => { if (nestedOption.kind == "empty") return emptyOption(); if (nestedOption.content.kind == "empty") return emptyOption(); return fullOption(nestedOption.content.content); }) } static unit = (unstructuredValue: a) => { return fullOption(unstructuredValue); } static zero = (): Option => { return emptyOption(); } } const mapOption = (f: FunType): FunType, Option> => { return Fun((option: Option) => { if (option.kind == "empty") { return emptyOption(); } return fullOption(f(option.content)); }) } const bindOption = (source: Option, cont: FunType>): Option => { return mapOption(cont).next(OptionFunctors.funJoin()) (source); } const saveDivide = (x: Option, y: Option): Option => { return x.then((xValue) => { return y.then((yValue) => { if (yValue == 0) { return emptyOption(); } return fullOption(xValue / yValue); }) }) } // let exmp = saveDivide(fullOption(3), saveDivide(fullOption(3), fullOption(0))); // // Either // type Either = { kind: "left", value: a } | { kind: "right", value: b } const eitherL = (): FunType> => Fun((input: a) => ({ kind: "left", value: input })); const eitherR = (): FunType> => Fun((input: b) => ({ kind: "right", value: input })); const mapEither = (fL: FunType, fR: FunType): FunType, Either> => { return Fun((input: Either) => { if (input.kind === "left") { return fL.next(eitherL())(input.value); } return fR.next(eitherR())(input.value); }) } type MyException = { msg: string; } interface ServerConnection { ip: string; //ip address hello: string; //hello message } const servers: Array = [ { ip: "11.11.11.11", hello: "Connected to EU server!" }, { ip: "22.22.22.22", hello: "Connected to Asia Server" }, { ip: "33.33.33.33", hello: "Connected to US Server" }, ] const connect = (): FunType> => { return Fun((ip: string) => { const prob: number = Math.random(); if (prob < 0.8) { return eitherL()({ ip: "11", hello: "2323" }); } else { return eitherR()({ msg: "Could not connect to server" }); } }); } // // Process // type Process = FunType; const mapProcess = (f: FunType): FunType, Process> => { return Fun((initialprocess) => { return Fun((initialState) => { const [newState, result] = initialprocess(initialState); const mappedResult = f(result); return [newState, mappedResult]; }) }) } class ProcessFunctors { static join = (outerProcess: Process>): Process => { return Fun((outerState) => { const [innerState, innerProcess] = outerProcess(outerState); return innerProcess(innerState); }) } // static funJoin = (): FunType>, Option> => { // return Fun((nestedOption: Option>) => { // if (nestedOption.kind == "empty") return emptyOption(); // if (nestedOption.content.kind == "empty") return emptyOption(); // return fullOption(nestedOption.content.content); // }) // } static unit = (unstructuredValue: T): Process => { return Fun((state) => { return [state, unstructuredValue]; }) } } type ProcessWithError = FunType>;