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; } const Conditional = function(_if: FunType, _then: FunType, _else: FunType): FunType { return Fun((x) => { if (_if(x)) { return _then(x); } else { return _else(x); } }) } // // 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) => x + "!"); const saveSqrt = Fun((a: number) => a > 0 ? Math.sqrt(a) : Math.abs(Math.sqrt(a))); // // 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(_ => _); } // Another way of writing this functions const genericId = (): FunType => Fun(_ => _); // Even one more way of writing this function const genericId2 = function(): 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! // type Pair = { fst: a, snd: b } const myPair: Pair = { fst: 1, snd: "Hello" } function pairFst(): FunType, a> { return Fun((pair: Pair) => { return pair.fst; }) } const pairSnd = function(): FunType, b> { return Fun((pair: Pair) => { return pair.snd; }) } const pairMap = function(f1: FunType, f2: FunType): FunType, Pair> { return Fun((pair: Pair) => { return { fst: f1(pair.fst), snd: f2(pair.snd) } }) } type WithNum = Pair; const withNumSingle: WithNum = { fst: "yeey", snd: 3 } const withNumDouble: WithNum> = { fst: { fst: "Hoi!", snd: 3, }, snd: 1, } const mapWithNum = function(f: FunType): FunType, WithNum> { return pairMap(f, id()); } const joinWithNum = function(): FunType>, WithNum> { return Fun(num => { return { fst: num.fst.fst, snd: num.snd + num.fst.snd } }) } const incrementWithNum = function(): FunType, WithNum> { return Fun((num: WithNum) => { return { fst: num.fst, snd: num.snd + 1 } }) } console.log( joinWithNum().next(incrementWithNum()).next(incrementWithNum()) (withNumDouble) ); const test123 = "sdsdds";