School/excersise.ts
2023-04-03 13:57:42 +02:00

381 lines
11 KiB
TypeScript

type FunType<input, output> = {
(inputValue: input): output;
next: <nextOuput>(nextFunction: FunType<output, nextOuput>) => FunType<input, nextOuput>;
repeat: (this: FunType<input, input>, count: number) => FunType<input, input>;
repeatUntil: (this: FunType<input, input>, condition: FunType<input, boolean>) => FunType<input, input>;
}
const Fun = <input, output>(inputLamba: (_: input) => output): FunType<input, output> => {
const func = inputLamba as FunType<input, output>;
func.next = function<nextOutput>(nextFunction: FunType<output, nextOutput>): FunType<input, nextOutput> {
return Fun(inputValue => nextFunction(inputLamba(inputValue)));
}
func.repeat = function(this: FunType<input, input>, count: number): FunType<input, input> {
if (count == 0) {
return Fun((x: input) => x);
}
return this.next(this.repeat(count - 1));
}
func.repeatUntil = function(this: FunType<input, input>, condition: FunType<input, boolean>): FunType<input, input> {
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<input, output>(_if: FunType<input, boolean>, _then: FunType<input, output>, _else: FunType<input, output>): FunType<input, output> {
return Fun((x) => {
if (_if(x)) {
return _then(x);
} else {
return _else(x);
}
})
}
//
// Some FunType lamdas
//
const isPositive: FunType<number, boolean> = Fun((x: number) => x > 0);
const isEven: FunType<number, boolean> = Fun((x: number) => x % 2 == 0);
const incr: FunType<number, number> = Fun((x: number) => x + 1);
const double: FunType<number, number> = Fun((x: number) => x * 2);
const decr: FunType<number, number> = Fun((x: number) => x - 1);
const convert: FunType<number, string> = Fun((x: number) => String(x));
const exclaim: FunType<string, string> = 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: <a>() => FunType<a, a> = () => Fun(x => x);
function id<a>(): FunType<a, a> {
return Fun(_ => _);
}
// Another way of writing this functions
const genericId = <a>(): FunType<a, a> => Fun(_ => _);
// Even one more way of writing this function
const genericId2 = function<a>(): FunType<a, a> {
return Fun(_ => _);
}
// Example usage 1
let exampleId = id<number>().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<Unit, number> = Fun(_ => 0);
const idString: FunType<Unit, string> = 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; // Content
counter: number; // For Example: amount of times object has been changed or mapped.
}
// Increments the value of counter with 1
function incrCountainer<content>(countainer: Countainer<content>): Countainer<content> {
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<content, newContent>(countainer: Countainer<content>, transformer: FunType<content, newContent>): Countainer<newContent> {
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<content, newContent>(transformer: FunType<content, newContent>): FunType<Array<content>, Array<newContent>> {
return Fun((array: Array<content>) => 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<content, newContent>(transformer: FunType<content, newContent>): FunType<Countainer<content>, Countainer<newContent>> {
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<Message, Message>(c => ({ username: newUsername, likes: c.likes, message: c.message }));
const incrLikes = () => Fun<Message, Message>(c => ({ username: c.username, likes: c.likes + 1, message: c.message }));
// We could also write these functions like this
function setMessage (msg: string): FunType<Message, Message> {
return Fun(c => {
return {
username: c.username,
likes: c.likes,
message: msg
}
})
}
// Countainer where content is of Message Type
let messageCountainer: Countainer<Message> = {
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<Countainer<Message>>, Array<Countainer<Message>>> = mapArray(mapCountainer(incrLikes()));
// Create array
const messages: Array<Countainer<Message>> = [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<number> = [1, 2, 3];
const numArray2: Array<number> = [2, 3, 4];
const numArray3: Array<number> = [5, 6, 7];
const numArray4: Array<number> = [];
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<number>().next(incr).next(incr).next(incr) (0);
let monoidTest2 = id<number>().next(incr.next(incr).next(incr)) (0);
let monoidTest3 = id<number>().next(incr.next(incr)).next(incr) (0);
// Therefore: monoidTest1 == monoidTest2 == monoidTest3
let monoidTest4 = incr.next(id<number>()) (0);
let monoidTest5 = id<number>().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<a, b> = {
fst: a,
snd: b
}
const myPair: Pair<number, string> = {
fst: 1,
snd: "Hello"
}
function pairFst<a, b>(): FunType<Pair<a, b>, a> {
return Fun((pair: Pair<a, b>) => {
return pair.fst;
})
}
const pairSnd = function<a, b>(): FunType<Pair<a, b>, b> {
return Fun((pair: Pair<a, b>) => {
return pair.snd;
})
}
const pairMap = function<a, b, a1, b1>(f1: FunType<a, a1>, f2: FunType<b, b1>): FunType<Pair<a, b>, Pair<a1, b1>> {
return Fun((pair: Pair<a, b>) => {
return {
fst: f1(pair.fst),
snd: f2(pair.snd)
}
})
}
type WithNum<a> = Pair<a, number>;
const withNumSingle: WithNum<string> = {
fst: "yeey",
snd: 3
}
const withNumDouble: WithNum<WithNum<string>> = {
fst: {
fst: "Hoi!",
snd: 3,
},
snd: 1,
}
const mapWithNum = function<a, b>(f: FunType<a, b>): FunType<WithNum<a>, WithNum<b>> {
return pairMap(f, id<number>());
}
const joinWithNum = function<a>(): FunType<WithNum<WithNum<a>>, WithNum<a>> {
return Fun(num => {
return {
fst: num.fst.fst,
snd: num.snd + num.fst.snd
}
})
}
const incrementWithNum = function<a>(): FunType<WithNum<a>, WithNum<a>> {
return Fun((num: WithNum<a>) => {
return {
fst: num.fst,
snd: num.snd + 1
}
})
}
console.log(
joinWithNum().next(incrementWithNum()).next(incrementWithNum()) (withNumDouble)
);
const test123 = "sdsdds";