├── babel.config.js ├── .gitignore ├── src ├── HKT.ts ├── Identity.ts ├── tests │ ├── Applicative.spec.ts │ ├── Identity.spec.ts │ ├── ApplicativeV2.spec.ts │ ├── utils.spec.ts │ ├── State.spec.ts │ ├── Reader.spec.ts │ └── Either.spec.ts ├── State.ts ├── Reader.ts ├── utils.ts ├── Applicative.ts ├── Applicative2.ts └── Either.ts ├── tsconfig.json ├── package.json └── README.md /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { targets: { node: 'current' } }], 4 | '@babel/preset-typescript', 5 | ], 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # misc 5 | .DS_Store 6 | .env.local 7 | .env.development.local 8 | .env.test.local 9 | .env.production.local 10 | .cache 11 | .directory -------------------------------------------------------------------------------- /src/HKT.ts: -------------------------------------------------------------------------------- 1 | // Higher Kinded Types 2 | 3 | // Unique type: 4 | export interface URI2HKT {} 5 | export type URIS = keyof URI2HKT 6 | export type HKT = URI2HKT[URI] 7 | 8 | // A | B: 9 | export interface URI2HKT2 {} 10 | export type URIS2 = keyof URI2HKT2 11 | export type HKT2 = URI2HKT2[URI] 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "ES5", 5 | "lib": ["dom", "dom.iterable", "esnext", "scripthost"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "CommonJS", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve" 17 | }, 18 | "include": ["src"] 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "naive_functional_programming", 3 | "version": "0.0.1", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "author": "Rafael Campos", 7 | "devDependencies": { 8 | "@babel/core": "^7.12.13", 9 | "@babel/preset-env": "^7.12.13", 10 | "@babel/preset-typescript": "^7.12.13", 11 | "@types/jest": "^26.0.20", 12 | "babel-jest": "^26.6.3", 13 | "jest": "^26.6.3", 14 | "ts-node": "^9.1.1", 15 | "typescript": "^4.1.5", 16 | "typescript-pattern-matching": "^1.0.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Identity.ts: -------------------------------------------------------------------------------- 1 | export const URI = 'Identity' 2 | 3 | export interface Identity { 4 | map: (f: (x: A) => B) => Identity 5 | chain: (f: (x: A) => Identity) => Identity 6 | fold: (f: (x: A) => B) => B 7 | inspect: () => string 8 | } 9 | 10 | export const identity = (value: A): Identity => ({ 11 | map: (f: (x: A) => B) => identity(f(value)), 12 | chain: (f: (x: A) => Identity) => f(value), 13 | fold: (f: (x: A) => B) => f(value), 14 | inspect: () => `Identity(${value})`, 15 | }) 16 | 17 | declare module './HKT' { 18 | interface URI2HKT { 19 | Identity: Identity 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/tests/Applicative.spec.ts: -------------------------------------------------------------------------------- 1 | import { identity } from '../Identity' 2 | import { applicative } from '../Applicative' 3 | 4 | const add10 = (x: number) => x + 10 5 | 6 | const three = identity(3) 7 | 8 | const id = (x: any) => x 9 | 10 | // Composition 11 | 12 | const compositionTest = 13 | three.map(add10).inspect() === 14 | applicative(add10) 15 | .ap(three) 16 | .inspect() 17 | 18 | // Identity 19 | 20 | const identityTest = 21 | applicative(id) 22 | .ap(three) 23 | .inspect() === three.inspect() 24 | 25 | describe('Applicative', () => { 26 | it('should preserve composition', () => { 27 | expect(compositionTest).toBe(true) 28 | }) 29 | it('should preserve identity', () => { 30 | expect(identityTest).toBe(true) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/State.ts: -------------------------------------------------------------------------------- 1 | export const URI = "State"; 2 | 3 | export interface State { 4 | runWith: (s: S) => [S, A]; 5 | chain: (st: (a: A) => State) => State; 6 | map: (f: (a: A) => B) => State; 7 | inspect: () => string; 8 | } 9 | 10 | export const state = (f: (s: S) => [S, A]): State => ({ 11 | runWith: (s: S) => f(s), 12 | chain(st: (a: A) => State) { 13 | return state(s1 => { 14 | const [s2, a] = this.runWith(s1); 15 | return st(a).runWith(s2); 16 | }); 17 | }, 18 | map(f: (a: A) => B) { 19 | return state(s1 => { 20 | const [s2, a] = this.runWith(s1); 21 | return [s2, f(a)]; 22 | }); 23 | }, 24 | inspect: () => "State" 25 | }); 26 | 27 | declare module "./HKT" { 28 | interface URI2HKT2 { 29 | State: State; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Reader.ts: -------------------------------------------------------------------------------- 1 | export const URI = "Reader"; 2 | 3 | export interface Reader { 4 | addConfig: (c: C) => [C, A]; 5 | chain: (st: (a: A) => Reader) => Reader; 6 | map: (f: (a: A) => B) => Reader; 7 | inspect: () => string; 8 | } 9 | 10 | export const reader = (f: (c: C) => [C, A]): Reader => ({ 11 | addConfig: (c: C) => f(c), 12 | chain(r: (a: A) => Reader) { 13 | return reader(c => { 14 | const [_, a] = this.addConfig(c); 15 | return r(a).addConfig(c); 16 | }); 17 | }, 18 | map(f: (a: A) => B) { 19 | return reader(c => { 20 | const [_, a] = this.addConfig(c); 21 | return [c, f(a)]; 22 | }); 23 | }, 24 | inspect: () => `Reader` 25 | }); 26 | 27 | declare module "./HKT" { 28 | interface URI2HKT2 { 29 | Reader: Reader; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Either, either } from "./Either"; 2 | import { Identity, identity } from "./Identity"; 3 | 4 | export const exist = (x: any) => x !== undefined && x !== null; 5 | 6 | export const fromNullable = (x?: A): Either => 7 | exist(x) 8 | ? either({ tag: "right", value: x as A }) 9 | : either({ tag: "left", value: undefined }); 10 | 11 | export const tryCatch = (fn: (arg: A) => B, arg: A): Either => { 12 | try { 13 | return either({ tag: "right", value: fn(arg) }); 14 | } catch (error) { 15 | return either({ tag: "left", value: error }); 16 | } 17 | }; 18 | 19 | // Traverse an array of Identities: 20 | export const traverse = (ar: Identity[]): Identity => 21 | identity(ar.map(i => i.fold(x => x))); 22 | 23 | // Natural transformations: 24 | export const identityToEither = (i: Identity) => 25 | i.fold(x => either({ tag: "right", value: x })); 26 | -------------------------------------------------------------------------------- /src/tests/Identity.spec.ts: -------------------------------------------------------------------------------- 1 | import { identity } from '../Identity' 2 | 3 | // Testing Laws: 4 | 5 | const f = (x: number) => x * 10 6 | const g = (x: number) => x - 10 7 | const id = (x: any) => x 8 | 9 | // Composition 10 | 11 | const compositionTest = 12 | identity(10) 13 | .map(f) 14 | .map(g) 15 | .inspect() === 16 | identity(10) 17 | .map(x => g(f(x))) 18 | .inspect() 19 | 20 | // Identity 21 | 22 | const identityTest = 23 | identity(10) 24 | .map(id) 25 | .inspect() === identity(10).inspect() 26 | 27 | // Chain & Fold: 28 | 29 | const extendedMethodsTest = 30 | identity(10) 31 | .chain(n => identity(n + 10)) 32 | .fold(id) === 20 33 | 34 | describe('Identity', () => { 35 | it('should preserve composition', () => { 36 | expect(compositionTest).toBe(true) 37 | }) 38 | it('should preserve identity', () => { 39 | expect(identityTest).toBe(true) 40 | }) 41 | it('should chain and fold correctly', () => { 42 | expect(extendedMethodsTest).toBe(true) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /src/Applicative.ts: -------------------------------------------------------------------------------- 1 | import { URIS, HKT } from './HKT' 2 | 3 | // Applicatives for Functor: 4 | 5 | export interface Applicative { 6 | ap: (fa: HKT) => HKT 7 | } 8 | 9 | export interface ApplicativeC2 { 10 | ap: (fa: HKT) => Applicative 11 | } 12 | 13 | export interface ApplicativeC3 { 14 | ap: (fa: HKT) => ApplicativeC2 15 | } 16 | 17 | export const applicative = (value: (x: A) => B): Applicative => ({ 18 | ap: (fa: HKT) => fa.map(value), 19 | }) 20 | 21 | export const applicative2 = ( 22 | value: (x: A) => (y: B) => C 23 | ): ApplicativeC2 => ({ 24 | ap: (fa: HKT) => applicative(fa.fold(value)), 25 | }) 26 | 27 | export const applicative3 = ( 28 | value: (x: A) => (y: B) => (z: C) => D 29 | ): ApplicativeC3 => ({ 30 | ap: (fa: HKT) => applicative2(fa.fold(value)), 31 | }) 32 | -------------------------------------------------------------------------------- /src/tests/ApplicativeV2.spec.ts: -------------------------------------------------------------------------------- 1 | import { either } from "../Either"; 2 | import { applicative2 } from "../Applicative2"; 3 | 4 | const add10 = (x: number) => x + 10; 5 | 6 | const three = either({ tag: "right", value: 3 }); 7 | const error = either({ tag: "left", value: "Error" }); 8 | 9 | const id = (x: any) => x; 10 | 11 | // Composition 12 | 13 | const compositionTest1 = 14 | three.map(add10).inspect() === 15 | applicative2(add10) 16 | .ap(three) 17 | .inspect(); 18 | 19 | const compositionTest2 = 20 | error.map(add10).inspect() === 21 | applicative2(add10) 22 | .ap(error) 23 | .inspect(); 24 | 25 | // Identity 26 | 27 | const identityTest1 = 28 | applicative2(id) 29 | .ap(three) 30 | .inspect() === three.inspect(); 31 | 32 | const identityTest2 = 33 | applicative2(id) 34 | .ap(error) 35 | .inspect() === error.inspect(); 36 | 37 | describe("ApplicativeV2", () => { 38 | it("should preserve composition", () => { 39 | expect(compositionTest1).toBe(true); 40 | expect(compositionTest2).toBe(true); 41 | }); 42 | it("should preserve identity", () => { 43 | expect(identityTest1).toBe(true); 44 | expect(identityTest2).toBe(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/Applicative2.ts: -------------------------------------------------------------------------------- 1 | import { HKT2, URIS2 } from './HKT' 2 | 3 | // Applicatives for Functors: 4 | // V2 stands for two values and C[N] for the number of curried arguments: 5 | 6 | export interface Applicative2 { 7 | ap: (fea: HKT2) => HKT2 8 | } 9 | 10 | export interface Applicative2C2 { 11 | ap: (fea: HKT2) => Applicative2 12 | } 13 | 14 | export interface Applicative2C3 { 15 | ap: (fea: HKT2) => Applicative2C2 16 | } 17 | 18 | const id = (x: any) => x 19 | 20 | export const applicative2 = (value: (x: A) => B): Applicative2 => ({ 21 | ap: (fea: HKT2) => fea.map(value), 22 | }) 23 | 24 | export const applicative2C2 = ( 25 | value: (x: A) => (y: B) => C 26 | ): Applicative2C2 => ({ 27 | ap: (fea: HKT2) => 28 | applicative2(fea.fold(id, value)), 29 | }) 30 | 31 | export const applicativeV2C3 = ( 32 | value: (x: A) => (y: B) => (z: C) => D 33 | ): Applicative2C3 => ({ 34 | ap: (fea: HKT2) => 35 | applicative2C2(fea.fold(id, value)), 36 | }) 37 | -------------------------------------------------------------------------------- /src/tests/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { tryCatch, fromNullable, identityToEither, traverse } from "../utils"; 2 | import { identity } from "../Identity"; 3 | 4 | const errorMessage = "Zero is not a valid divisor"; 5 | 6 | const divide10By = (x: number) => { 7 | if (x === 0) throw errorMessage; 8 | return 10 / x; 9 | }; 10 | 11 | const traverseTest = 12 | traverse([identity(1), identity(2), identity(3)]).inspect() === 13 | identity([1, 2, 3]).inspect(); 14 | 15 | // Natural transformation: 16 | const ntLaw = 17 | identityToEither(identity(10)) 18 | .map(x => x * x) 19 | .inspect() === identityToEither(identity(10).map(x => x * x)).inspect(); 20 | 21 | describe("fromNullable", () => { 22 | it("should return an Either", () => { 23 | expect(fromNullable().inspect()).toBe(`Left(undefined)`); 24 | expect(fromNullable(10).inspect()).toBe(`Right(10)`); 25 | }); 26 | }); 27 | 28 | describe("tryCatch", () => { 29 | it("should return an Either", () => { 30 | expect(tryCatch(divide10By, 0).inspect()).toBe(`Left(${errorMessage})`); 31 | expect(tryCatch(divide10By, 10).inspect()).toBe("Right(1)"); 32 | }); 33 | }); 34 | 35 | describe("traverse", () => { 36 | it("should return an Identity", () => { 37 | expect(traverseTest).toBe(true); 38 | }); 39 | }); 40 | 41 | describe("Natural transformation: identityToEither", () => { 42 | it("should respect the nt law", () => { 43 | expect(ntLaw).toBe(true); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/tests/State.spec.ts: -------------------------------------------------------------------------------- 1 | import { state } from '../State' 2 | // Testing Laws: 3 | 4 | const initialState = 10 5 | const onState = (x: number): [number, number] => [x * x, x + 10] 6 | const f = (x: number) => x * 10 7 | const g = (x: number) => x - 10 8 | const id = (x: any) => x 9 | 10 | // Composition 11 | 12 | const compositionTest = 13 | JSON.stringify( 14 | state(onState) 15 | .map(f) 16 | .map(g) 17 | .runWith(initialState) 18 | ) === 19 | JSON.stringify( 20 | state(onState) 21 | .map(x => g(f(x))) 22 | .runWith(initialState) 23 | ) 24 | 25 | // Identity 26 | 27 | const identityTest = 28 | JSON.stringify( 29 | state(onState) 30 | .map(id) 31 | .runWith(initialState) 32 | ) === JSON.stringify(state(onState).runWith(initialState)) 33 | 34 | // Chain & Fold: 35 | const s1 = state(onState) 36 | const s2 = (a: number) => state(x => [x + 10, a + 10]) 37 | const s3 = (b: number) => state(x => [x * 10, b + 10]) 38 | 39 | // state: 10 -> 100 -> 110 -> 1100 40 | // value: 10 -> 20 -> 30 -> 40 41 | 42 | const [st, value] = s1 43 | .chain(s2) 44 | .chain(s3) 45 | .runWith(initialState) 46 | 47 | const chainTest = st === 1100 && value === 40 48 | 49 | describe('State', () => { 50 | it('should preserve composition', () => { 51 | expect(compositionTest).toBe(true) 52 | }) 53 | it('should preserve identity', () => { 54 | expect(identityTest).toBe(true) 55 | }) 56 | it('should chain correctly', () => { 57 | expect(chainTest).toBe(true) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /src/tests/Reader.spec.ts: -------------------------------------------------------------------------------- 1 | import { reader } from '../Reader' 2 | // Testing Laws: 3 | 4 | const config = { env: 'development' } 5 | type Config = typeof config 6 | const withConfig = (c: Config): [Config, number] => [ 7 | c, 8 | c.env === 'development' ? 10 : 0, 9 | ] 10 | const f = (x: number) => x * 10 11 | const g = (x: number) => x - 10 12 | const id = (x: any) => x 13 | 14 | // Composition 15 | 16 | const compositionTest = 17 | JSON.stringify( 18 | reader(withConfig) 19 | .map(f) 20 | .map(g) 21 | .addConfig(config) 22 | ) === 23 | JSON.stringify( 24 | reader(withConfig) 25 | .map(x => g(f(x))) 26 | .addConfig(config) 27 | ) 28 | 29 | // Identity 30 | 31 | const identityTest = 32 | JSON.stringify( 33 | reader(withConfig) 34 | .map(id) 35 | .addConfig(config) 36 | ) === JSON.stringify(reader(withConfig).addConfig(config)) 37 | 38 | // Chain & Fold: 39 | const r1 = reader(withConfig) 40 | const r2 = (a: number) => reader(c => [c, a + 10]) 41 | const r3 = (b: number) => reader(c => [c, b + 10]) 42 | 43 | // value: 10 -> 20 -> 30 44 | 45 | const [c, value] = r1 46 | .chain(r2) 47 | .chain(r3) 48 | .addConfig(config) 49 | 50 | const chainTest = JSON.stringify(c) === JSON.stringify(config) && value === 30 51 | 52 | describe('State', () => { 53 | it('should preserve composition', () => { 54 | expect(compositionTest).toBe(true) 55 | }) 56 | it('should preserve identity', () => { 57 | expect(identityTest).toBe(true) 58 | }) 59 | it('should chain correctly', () => { 60 | expect(chainTest).toBe(true) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /src/Either.ts: -------------------------------------------------------------------------------- 1 | import { match } from "typescript-pattern-matching"; 2 | 3 | export const URI = "Either"; 4 | 5 | type EitherValue = { tag: "left"; value: L } | { tag: "right"; value: R }; 6 | 7 | export interface Right { 8 | map: (f: (x: R) => B) => Right; 9 | chain: (f: (x: R) => Right) => Right; 10 | fold: (onLeft: (x: L) => B, onRight: (x: R) => C) => B | C; 11 | inspect: () => string; 12 | } 13 | 14 | export interface Left { 15 | map: (f: (x: R) => B) => Left; 16 | chain: (f: (x: R) => Left) => Left; 17 | fold: (onLeft: (x: L) => B, onRight: (x: R) => C) => B | C; 18 | inspect: () => string; 19 | } 20 | 21 | const right = (value: R): Right => ({ 22 | map: (f: (x: R) => B) => right(f(value)), 23 | chain: (f: (x: R) => Right) => f(value), 24 | fold: (onLeft: (x: L) => B, onRight: (x: R) => C) => onRight(value), 25 | inspect: () => `Right(${value})` 26 | }); 27 | 28 | const left = (value: L): Left => ({ 29 | map: (_: (x: R) => B) => left(value), 30 | chain: (_: (x: R) => Right) => left(value), 31 | fold: (onLeft: (x: L) => B, onRight: (x: R) => C) => onLeft(value), 32 | inspect: () => `Left(${value})` 33 | }); 34 | 35 | export type Either = Right | Left; 36 | 37 | export const either = ( 38 | taggedValue: EitherValue 39 | ): Left | Right => 40 | match, Right | Left>(taggedValue) 41 | .with({ tag: "left" }, ({ tag, value }) => left(value)) 42 | .with({ tag: "right" }, ({ tag, value }) => right(value)) 43 | .run(); 44 | 45 | declare module "./HKT" { 46 | interface URI2HKT2 { 47 | Either: Either; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/tests/Either.spec.ts: -------------------------------------------------------------------------------- 1 | import { either } from "../Either"; 2 | 3 | // Testing Laws: 4 | 5 | const f = (x: number) => x * 10; 6 | const g = (x: number) => x - 10; 7 | const id = (x: any) => x; 8 | 9 | // Composition 10 | 11 | const compositionTestRight = 12 | either({ tag: "right", value: 10 }) 13 | .map(f) 14 | .map(g) 15 | .inspect() === 16 | either({ tag: "right", value: 10 }) 17 | .map(x => g(f(x))) 18 | .inspect(); 19 | 20 | const compositionTestLeft = 21 | either({ tag: "left", value: "error" }) 22 | .map(f) 23 | .map(g) 24 | .inspect() === 25 | either({ tag: "left", value: "error" }) 26 | .map(x => g(f(x))) 27 | .inspect(); 28 | 29 | // Identity 30 | 31 | const identityTestRight = 32 | either({ tag: "right", value: 10 }) 33 | .map(id) 34 | .inspect() === 35 | either({ tag: "right", value: 10 }).inspect(); 36 | 37 | const identityTestLeft = 38 | either({ tag: "left", value: "error" }) 39 | .map(id) 40 | .inspect() === 41 | either({ tag: "left", value: "error" }).inspect(); 42 | 43 | // Chain & Fold: 44 | 45 | const extendedMethodsTestRight = 46 | either({ tag: "right", value: 10 }) 47 | .chain(n => either({ tag: "right", value: n + 10 })) 48 | .fold(id, id) === 20; 49 | 50 | const extendedMethodsTestLeft = 51 | either({ tag: "left", value: "error" }) 52 | .chain(n => either({ tag: "right", value: n + 10 })) 53 | .fold(id, id) === "error"; 54 | 55 | describe("Either", () => { 56 | it("should preserve composition", () => { 57 | expect(compositionTestRight).toBe(true); 58 | expect(compositionTestLeft).toBe(true); 59 | }); 60 | it("should preserve identity", () => { 61 | expect(identityTestRight).toBe(true); 62 | expect(identityTestLeft).toBe(true); 63 | }); 64 | it("should chain and fold correctly", () => { 65 | expect(extendedMethodsTestRight).toBe(true); 66 | expect(extendedMethodsTestLeft).toBe(true); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A naive approach to functional programming 2 | 3 | I'm trying to learn functional programming, and that could be quite overwhelming. 4 | 5 | Search for functional programming often results in a bunch of unreadable lingoes (for me at least), articles trying to wrap the F-word in new names like 'mappable', or people that do not understand it, but are writing tutorials either way. 6 | 7 | **That isn't what I'm trying to do here. What I'm doing is learn in public.** 8 | 9 | ## Some real functional programming content 10 | 11 | First, and that is worth even if you do not keep reading to the end, check this out: 12 | 13 | - [Professor Frisby Introduces Composable Functional JavaScript by Brian Lonsdorf](https://egghead.io/courses/professor-frisby-introduces-composable-functional-javascript) - This is an incredible free course, and is the source of most of the stuff you are going to see here, so do a favor to yourself and watch it. 14 | 15 | - [Category Theory by Bartosz Milewski](https://www.youtube.com/watch?v=I8LbkfSSR58&list=PLbgaMIhjbmEnaH_LTkxLI7FMa2HsnawM_) - If you're feeling adventurous and want to discover the crazy world of the Category Theory, watch this series of videos (I'm watching for the second time, and probably I'll need at least one more, but worth it). 16 | 17 | Now that I pointed you to people who know what they're talking about, I'm free to throw here that stuff that is in my head while I'm trying to figure that out. 18 | 19 | **Table of content** 20 | 21 | - [A naive approach to functional programming](#a-naive-approach-to-functional-programming) 22 | - [Some real functional programming content](#some-real-functional-programming-content) 23 | - [What?! Why?!](#what-why) 24 | - [Composition](#composition) 25 | - [Associativity](#associativity) 26 | - [Identity](#identity) 27 | - [Tools](#tools) 28 | - [Identity Functor](#identity-functor) 29 | - [Either](#either) 30 | - [Applicative](#applicative) 31 | - [Natural Transformation](#natural-transformation) 32 | - [Isomorphism](#isomorphism) 33 | - [Higher Kinded Types](#higher-kinded-types) 34 | - [I/O](#io) 35 | - [Algebraic Data Types](#algebraic-data-types) 36 | - [Products/Conjunctions - pairs, tuples, records](#products-conjunctions---pairs--tuples--records) 37 | - [Sums/Alternatives - Either](#sums-alternatives---either) 38 | - [Exponentials/Implication - Function types](#exponentials-implication---function-types) 39 | - [Unit/Truth - Void](#unit-truth---void) 40 | - [State Monad](#state-monad) 41 | - [Reader Monad](#reader-monad) 42 | - [Pattern Matching](#pattern-matching) 43 | - [Contributing](#contributing) 44 | - [License](#license) 45 | 46 | ## What?! Why?! 47 | 48 | As far as I understand, Category Theory is a way to try to structure things through abstraction. 49 | 50 | Instead of walking in the streets trying to draw a map, we use a satellite picture for that. 51 | 52 | We have these **objects** and the **relations** between them, and we're forbidden to look into the objects (not because we want to punish ourselves, but to create structures that serve to more than one type of category), so **all our assertions need to be inferred from those relations, called morphisms**. 53 | 54 | > Let's stop right there. Why on earth I need to engage in this math theory?! I'm not trying to understand the entire world through math. I want to become a better programmer. 55 | 56 | Ok, I'm with you on that. But do you remember that those math guys have this constraint of only seeing things through their relations? If you take **types as a Category and function as morphisms**, you end up with a bunch of knowledge on how to deal with functions and can apply that to make your code better. I'm talking about **composition and predictability**. 57 | 58 | So the main goal is to write **code that is predictable and easy to reason about**, and we can get that if we fit our functions and data structures into the Category Theory laws. 59 | 60 | ## Composition 61 | 62 | Category Theory is all about composition. We only have the morphism/arrows/functions to look at, since we're not allowed to check out the object's properties. So all the theory is based on these arrows and how to compose them. 63 | 64 | The most common graphic representation we get when we start to search for Category Theory is: 65 | 66 | ![Composition](https://res.cloudinary.com/alohawav/image/upload/v1582603319/Screen_Shot_2020-02-24_at_10.00.52_PM_ydxc0b.png) 67 | 68 | If we have a function from A to B and another one from B to C, we must have a function A to C: 69 | 70 | ```typescript 71 | const f = (x: string) => x.length > 0; 72 | const g = (x: boolean) => (x ? 1 : 0); 73 | // Composing g after f: 74 | const h = (x: string) => g(f(x)); 75 | ``` 76 | 77 | And there is some laws: 78 | 79 | ### Associativity 80 | 81 | Taking: 82 | 83 | ```typescript 84 | type f = (x: A) => B; 85 | type g = (x: B) => C; 86 | type h = (x: C) => D; 87 | ``` 88 | 89 | We have: `h . (g . f) == (h . g) . f == h . g . f` 90 | 91 | After all it all translates to `x => h(g(f(x)))` 92 | 93 | ### Identity 94 | 95 | For every object, there is always a function that returns itself (identity): `id = x => x` 96 | 97 | ## Tools 98 | 99 | First, we need to use **pure functions**. 100 | 101 | There is a bunch of rules to check a function as pure, but I'll stick with: **referential transparency**, that translates to I can swap my function invocation for the value it returns, like a lookup table. So, for that, no global variables, no side effects. 102 | 103 | > I knew it! I work with Web Apps, my life is side effects: DOM manipulation, users inputs, Ajax calls, are you crazy?! 104 | 105 | Relax, take a deep breath 🧘‍♂️. We'll continue to do all that, believe me. 106 | 107 | Also, we need **currying** that's a **partial application** where the end function always expect one argument (function arity 1). Languages like Haskel have that into it, but with JS we need to create or use a lib for that. 108 | 109 | Ok, but there are more than pure functions. I'll throw a bunch of **loose definitions** here, so we can feel more comfortable when I talk Category Theory terms (and of course, it's valid to remember that's what I got about them): 110 | 111 | - Functors = A type that implements a `map` (not related to iteration, but applying a function on its value) **preserving composition** (`F(x).map(f).map(g) === F(x).map(g(f(x)))`) and **identity** (`F(x).map(x => x) = F(x)`). 112 | 113 | - Pointed Functors = Functor that implements an `of` method (that is a way to throw a value inside our container). 114 | 115 | - Applicative Functors = Functor that implements the method `ap` (apply the value of one container on the value of another container, remember, the value can be a function). 116 | 117 | - Semigroups = A type that implements a `concat` method that is **associativity**, that means `'Hello' + ('World' + '!') = ('Hello' + 'World') + '!'` 118 | 119 | - Monoids = It's a semigroup that has an `empty` method. In other words, it's safe to work no matter the number of elements: `empty string + 'all fine' = 'all fine'` no errors in our dear console. 120 | 121 | - Monads = Is a pointed functor that implements a `flatMap` or `chain` method (instead of nested Monads, it'll return a unidimensional Monad); 122 | 123 | - Natural transformation = FunctorA(value) => FunctorB(value) 124 | 125 | - Isomorphism = You can change from a type to another and vice-versa without losing information. 126 | 127 | I already can see people coming with torches and forks, to punish me for all the nonsense that I put here. But hold your horses. **That's a living document from my learning process, and that's what I get so far**. 128 | 129 | > Ok, ok. But I'm starting to lose my interest. Can we see some code? I want to check how to implement all of that! 130 | 131 | Fair enough. Let's move on. 132 | 133 | ## Identity Functor 134 | 135 | Have you ever saw those Category Theory diagrams, where there is a dot, and an arrow that goes from that dot to itself? 136 | 137 | Those are identity functors. In our case, where our category is a set of types, they're endofunctors (because they map to the same set). 138 | 139 | > Ok, but why would I be interested in such a thing, a functor that maps to itself? 140 | 141 | Because that's our **ticket for the marvelous world of composition**. 142 | 143 | We're going to put our value inside this container (functor) and use its `map` method to apply a function on its value and then return another functor to keep that going, till we finish with our operations and redeem our result using `fold`. 144 | 145 | First, let's define our Identity Functor: 146 | 147 | ```typescript 148 | interface Identity { 149 | map: (f: (x: A) => B) => Identity; 150 | chain: (f: (x: A) => Identity) => Identity; 151 | fold: (f: (x: A) => B) => B; 152 | inspect: () => string; 153 | } 154 | 155 | const identity = (value: A): Identity => ({ 156 | map: (f: (x: A) => B) => identity(f(value)), 157 | chain: (f: (x: A) => Identity) => f(value), 158 | fold: (f: (x: A) => B) => f(value), 159 | inspect: () => `Identity(${value})` 160 | }); 161 | ``` 162 | 163 | There are five methods here: 164 | 165 | - **map**: We apply a function to our value and put the result back in a functor to keep composing. 166 | - **chain**: Imagine that the function that you want to `map` returns another `Identity`. In that case, we'll end up with `Identity(Identity(value))`. Chain flat that, resulting in a single `Identity(value)`. 167 | - **fold**: It's tough, but sooner or later, our value needs to leave the cozy and secure functor. 168 | - **inspect**: Friendly console. 169 | 170 | > Wait a minute. You said five methods, but there are only four here! 171 | 172 | Yeap, the `identity` call has the same effect of the `of` method, that is the way to insert the value inside our functor. 173 | 174 | ## Either 175 | 176 | And how about conditional constructs and error handler? Can functional programming improve that aspect of coding? 177 | 178 | Yes, it can! 179 | 180 | In this [talk](https://www.youtube.com/watch?v=E8I19uA-wGY) Scott Wlaschin uses this Railway Track model: 181 | 182 | ![Railway Track Model](https://res.cloudinary.com/alohawav/image/upload/v1581997913/railwayTrackModel_jfnynj.png) 183 | 184 | Here we have functions that can return two different things, and we have a functor that can help us with that called Either: 185 | 186 | ```typescript 187 | interface Either { 188 | map: (f: (x: R) => B) => Either; 189 | chain: (f: (x: R) => Either) => Either | Either; 190 | fold: (onLeft: (x: L) => B, onRight: (x: R) => C) => B | C; 191 | inspect: () => string; 192 | } 193 | 194 | const either = (left?: L, right?: R): Either => ({ 195 | map: (f: (x: R) => B) => 196 | exist(right) ? either(undefined, f(right as R)) : either(left), 197 | chain: (f: (x: R) => Either) => 198 | exist(right) ? f(right as R) : either(left), 199 | fold: (onLeft: (x: L) => B, onRight: (x: R) => C) => 200 | exist(right) ? onRight(right as R) : onLeft(left as L), 201 | inspect: () => (exist(right) ? `Right(${right})` : `Left(${left})`) 202 | }); 203 | ``` 204 | 205 | That serves to handle errors, if some pops up, we skip the other steps in our chain. But also for skipping unnecessary computation, imagine that we have three predicates, but we only need one to be true to pass to the next step. If the first one returns true, we don't need to compute the next two, we skip them and move on in our pipe. 206 | 207 | ## Applicative 208 | 209 | I want to go rad and put our curried functions in our pipeline. However, I don't want to `fold` just for that. 210 | 211 | To do so, I need to apply functors to functors: 212 | `F(x => x + 10) -> F(10) = F(20)`. 213 | 214 | In the end, we need a method `ap` in a Functor containing a function as value and call that passing another Functor with our expect argument into it: 215 | `ap: (functor) => functor.map(value)` 216 | 217 | A pointed functor with an `ap` method is called **applicative functor**. 218 | 219 | Typing that with TypeScript end up being impossible for me (TypeScript does not have higher-kinded types like Haskel, and I got lost within so many letters using HKT like in [fp-ts](https://github.com/gcanti/fp-ts)), so I cheated a little bit: 220 | 221 | ```typescript 222 | interface Applicative { 223 | ap: (i: Identity) => Identity; 224 | } 225 | 226 | interface Applicative2 { 227 | ap: (i: Identity) => Applicative; 228 | } 229 | 230 | interface Applicative3 { 231 | ap: (i: Identity) => Applicative2; 232 | } 233 | 234 | const applicative = (value: (x: A) => B): Applicative => ({ 235 | ap: (i: Identity) => i.map(value) 236 | }); 237 | 238 | const applicative2 = ( 239 | value: (x: A) => (y: B) => C 240 | ): Applicative2 => ({ 241 | ap: (i: Identity) => applicative(i.fold(value)) 242 | }); 243 | 244 | const applicative3 = ( 245 | value: (x: A) => (y: B) => (z: C) => D 246 | ): Applicative3 => ({ 247 | ap: (i: Identity) => applicative2(i.fold(value)) 248 | }); 249 | ``` 250 | 251 | Applicative Functors are not only about currying but also they help us parallelism. In a case where we have a couple of fetch calls, and we wrap them in a functor: 252 | 253 | ```typescript 254 | const synchronousLongTask = () => { 255 | // ... 256 | return identity("txt"); 257 | }; 258 | 259 | // sequential: 260 | synchronousLongTask().chain(t1 => 261 | synchronousLongTask().map(t2 => synchronousLongTask().map(t3 => t1 + t2 + t3)) 262 | ); 263 | 264 | // We have this waterfall effect, where we need to wait for the first `synchronousLongTask` 265 | // return to call the next one and so on. 266 | // But in our example, the calls do not depend on the predecessor. 267 | 268 | // They can be called in parallel: 269 | 270 | const composeTxt = (txt1: string) => (txt2: string) => (txt3: string) => 271 | txt1 + txt2 + txt3; 272 | 273 | applicative3(composeTxt) 274 | .ap(synchronousLongTask()) 275 | .ap(synchronousLongTask()) 276 | .ap(synchronousLongTask()); 277 | ``` 278 | 279 | ## Natural Transformation 280 | 281 | We saw that we could pass a function to a map method, let's say `a => b`, and get `F(a) => F(b)`. But how about on the Functor itself (`F(a) => G(a)`)? 282 | 283 | Let's use our Identity and Either: 284 | 285 | ```typescript 286 | const identityToEither = (i: Identity) => 287 | i.fold(x => either(undefined, x)); 288 | ``` 289 | 290 | In this point we already know that we need to respect some law to fit in the predictable Category world: 291 | 292 | `nt(x).map(f) === nt(x.map(f))` 293 | 294 | So, we need to put our function to the test: 295 | 296 | ```typescript 297 | identityToEither(identity(10)) 298 | .map(x => x * x) 299 | .inspect() === identityToEither(identity(10).map(x => x * x)).inspect(); 300 | 301 | // true (fireworks) 302 | ``` 303 | 304 | Finally, now I can understand this: 305 | 306 | ![Natural Transformation](https://res.cloudinary.com/alohawav/image/upload/v1582081476/nt_nxaoek.png) 307 | 308 | Follow the path ➡️ ⬇️ (red) is equal to go ⬇️ ➡️ (blue): 309 | 310 | `nt(F(a).map(f)) === nt(F(a)).map(f) === G` 311 | 312 | And of course, there is some more pro stuff like: 313 | 314 | ⬇️ ↘️ ➡️ + P 315 | 316 | ⬆️ ⬆️ ⬇️ ⬇️ ⬅️ ➡️ ⬅️ ➡️ + B + A + Start 317 | 318 | > Haha! Pretty funny, it's a Konami Code, haha... But wait, I'm losing focus here. Why do I want to transform a functor in another? 319 | 320 | You don't want to hammer a nail with a saw or cut a log with a hammer. In the same way, you use different functors for each type of task. 321 | 322 | However, the functions of your pipeline can return different functors, and we have Natural Transformations at our disposal to fit them in a new functor when we need the methods that it has to offer. Like we took a battery from a drilling machine into a circular saw (Okay, enough of analogies). 323 | 324 | ## Isomorphism 325 | 326 | Ok by now, we're starting to get the gist of morphisms (arrows), so let's put some spicy on it. 327 | 328 | We saw a bunch of arrows from `a` to `b`. But what if we also have a morphism from `b` to `a`? 329 | 330 | In this case, **they are no equal, but isomorphic**. We can map `a -> b` and `b -> a` **without losing information**. 331 | 332 | > What?! 333 | 334 | I guess an example it's better than all my gibresh: 335 | 336 | ```typescript 337 | const insert = x => [x]; 338 | const extract = a => a[0]; 339 | 340 | const start = 10; 341 | const advanceOneSpace = insert(start); 342 | const goBackOneSpace = extract(advanceOneSpace); 343 | 344 | start === goBackOneSpace; // True - I guess we're a stuck in this Monopoly game. 345 | ``` 346 | 347 | That gives us the ability to use a natural transformation to temporarily put our value in another Functor, solve a task that suits its peculiarities, and then put it back into the original Functor. 348 | 349 | ## Higher Kinded Types 350 | 351 | Till now, I'm dodging the TS inability to deal with generics of generics (F< A < B >>). 352 | 353 | Here an explanation of what is HKT and how we can simulate its behavior in TS: [Higher kinded types in TypeScript, static and fantasy land by gcanti](https://medium.com/@gcanti/higher-kinded-types-in-typescript-static-and-fantasy-land-d41c361d0dbe). 354 | 355 | It seems it's time to try that out: 356 | 357 | So now we have a Record of types. We can define them using HKT<[Identifier], [Type]> for functors holding values from a single type. Or HKT<[Identifier], [TypeA], [TypeB]> for functors that can hold A|B. 358 | 359 | ```typescript 360 | // Higher Kinded Types 361 | 362 | // Unique type: 363 | export interface URI2HKT {} 364 | export type URIS = keyof URI2HKT; 365 | export type HKT = URI2HKT[URI]; 366 | 367 | // A | B: 368 | export interface URI2HKT2 {} 369 | export type URIS2 = keyof URI2HKT2; 370 | export type HKT2 = URI2HKT2[URI]; 371 | ``` 372 | 373 | Like gcanti show in his Medium, I'm using [Module Augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) to add each one of my functors: 374 | 375 | ```typescript 376 | // src/Identity.ts: 377 | declare module "./HKT" { 378 | interface URI2HKT { 379 | Identity: Identity; 380 | } 381 | } 382 | 383 | // src/Either.ts: 384 | declare module "./HKT" { 385 | interface URI2HKT2 { 386 | Either: Either; 387 | } 388 | } 389 | ``` 390 | 391 | With that, I changed my first version of Applicative, that only can handle Identity Functors, for any Functor A, and create a new Applicative2 to deal with Functor A|B: 392 | 393 | ```typescript 394 | // src/Applicative.ts: 395 | export interface Applicative { 396 | ap: (fa: HKT) => HKT; 397 | } 398 | 399 | export const applicative = (value: (x: A) => B): Applicative => ({ 400 | ap: (fa: HKT) => fa.map(value) 401 | }); 402 | 403 | // src/Applicative2.ts: 404 | export interface Applicative2 { 405 | ap: (fea: HKT2) => HKT2; 406 | } 407 | 408 | export const applicative2 = (value: (x: A) => B): Applicative2 => ({ 409 | ap: (fea: HKT2) => fea.map(value) 410 | }); 411 | ``` 412 | 413 | ## I/O 414 | 415 | Bear with me. That will be a crazy trip. 416 | 417 | I was researching about I/O. Especially how to fit user inputs (that can be everything) and system outputs (functions that return void) in our functional world. 418 | 419 | First, I find articles assuming that's a side effect, and I/O returns a thunk (delayed calculation). So that serves as a flag (**I'll leave the predictable world and go rad, so only call me in a safe place**). 420 | 421 | However, I find a different approach to this problem: 422 | 423 | In this [video](https://www.youtube.com/watch?v=fCoQb-zqYDI), Tsoding reimplements the I/O monad, trying to answer that same question. 424 | 425 | Maybe we should change or perspective over the problem. Would a function that receives the state of the World and returns the state of the World be pure? 426 | 427 | ```typescript 428 | type IO = (w: World) => World; 429 | ``` 430 | 431 | ![math](https://media.giphy.com/media/26xBI73gWquCBBCDe/giphy.gif) 432 | 433 | It's like instead of `any` we had a type `everything` and from it we can extract the user input, or insert a `console.log`. 434 | 435 | > I'm not even bothering to discuss the `state of World` part, but when I use I/O in Haskell, I'm not passing anything to I/O. 436 | 437 | Well, what if you are doing it, but Haskell hides that from you? 438 | 439 | > Why? 440 | 441 | Unless we're dealing with the multi-dimension scenario, you have a single World that is being modified and passed to another function. 442 | 443 | World1 -> (someOperation) -> World2 -> (someOperation) -> World3 444 | 445 | So we're not allowed to reuse World1. To prevent the user from doing that, Haskell makes the World inaccessible for the user. 446 | 447 | Either way, going into the crazy World thing, or only deferring the evaluation of a side effect, we can have segments in our code, where we only deal with pure functions and points where the I/O (synchronous) or Task (asynchronous) side effect happens. 448 | 449 | At the end (at least till the point I understand it), I/O is the same as creating a function to receive user input or read a file that returns Identity or Either: 450 | 451 | ```typescript 452 | const unsafe = (): Either => { 453 | // ex.: get user input, parse to an Int or return 'error' 454 | }; 455 | 456 | const safe = (input: Either) => { 457 | input 458 | .map(x => x + 10) 459 | .map(x => x * x) 460 | .fold( 461 | x => x, 462 | x => x 463 | ); 464 | }; 465 | 466 | safe(unsafe()); 467 | ``` 468 | 469 | ## Algebraic Data Types 470 | 471 | Following [this Bartosz's talk](https://www.youtube.com/watch?v=LkqTLJK2API&list=WL), we have four algebraic data types: 472 | 473 | - Unit 474 | - Products 475 | - Sums 476 | - Exponentials 477 | 478 | Their names correspond to the number of possible values: 479 | 480 | Taking const setA = [true, false], const setB = [r,g, b]: 481 | 482 | - Unit: single value = `void`; 483 | - products: 6 possible results (2 \* 3) in `pair` = `[[true,r],[true,g],[true,b], [false,r],[false,g],[false,b]]` 484 | - sums: 5 possible results (2 + 3) in `A|B` = `[true, false, r, g, b]` 485 | - exponentials: 8 possible results (2^3), if we take the function type `B -> A` = `[r => true, r => false, g => true, g => false, b => true, b => false, _ => false, _ => true]` 486 | 487 | In type theory, whenever we define a type, we need to say how it's created (introduction) and how to use it (elimination), so let's try that out: 488 | 489 | ### Products/Conjunctions - pairs, tuples, records 490 | 491 | Taking a Pair, as an example, we need two values to create it (intoduction), and `first` and `second` methods give us access to each one of them (elimination): 492 | 493 | ```typescript 494 | const pair = (a: A) => (b: B) => ({ 495 | first: a, 496 | second: b 497 | }); 498 | ``` 499 | 500 | ### Sums/Alternatives - Either 501 | 502 | Here we have a union type: `A|B`. We can introduce using our naive `either` implementation: 503 | 504 | ```typescript 505 | const x = either(undefined, 10); 506 | const y = either("error"); 507 | ``` 508 | 509 | And to eliminate/use it we have the `fold` method: 510 | 511 | ```typescript 512 | x.fold(console.error, console.log); 513 | ``` 514 | 515 | ### Exponentials/Implication - Function types 516 | 517 | We implement them using lambdas `(a: A) => B` and eliminate them calling the function. 518 | 519 | ### Unit/Truth - Void 520 | 521 | It's always available so that you can use it everywhere. And there is no elimination since there is no value in it. 522 | 523 | > But how that relates to better programming? 524 | 525 | It's all about composing. We compose small functions to perform a complex operation, and we can compose types, to hold complex data. 526 | 527 | Scalar types (string, number, boolean) compose in more complex data types, like arrays, or objects. Types that, besides containing a value, bring with them a series of methods. 528 | 529 | Algebraic data types are ways to compose types, embeded with algebra laws, that can help us solve complex cases like define List recursively as: 530 | 531 | ```typescript 532 | type List = void | { head: T; tail: List }; 533 | ``` 534 | 535 | Which makes sense for a lazily evaluated language as Haskell. 536 | 537 | Speaking of that, check out the gvergnaud's implementation using generators: [lazy-list.js](https://gist.github.com/gvergnaud/6e9de8e06ef65e65f18dbd05523c7ca9). 538 | 539 | ## State Monad 540 | 541 | Finally, let's talk about the state. 542 | 543 | We saw that composition creates those pipes through which we pass our values and receive the results after they are processed. 544 | 545 | But what if they need a state besides their inputs to run? 546 | 547 | Of course, we'll not rely on a global variable. We're functional programmers (or at least we wish). Give us some credit. 548 | 549 | There is a way to keep all pure. We need to pass the state as input. 550 | 551 | The state will go through the pipes with our values. So given the same state and value, we'll get the same result. 552 | Here, as usual, my naive implementation of a State Monad: 553 | 554 | ```typescript 555 | export interface State { 556 | runWith: (s: S) => [S, A]; 557 | chain: (st: (a: A) => State) => State; 558 | map: (f: (a: A) => B) => State; 559 | } 560 | 561 | export const state = (f: (s: S) => [S, A]): State => ({ 562 | runWith: (s: S) => f(s), 563 | chain(st: (a: A) => State) { 564 | return state(s1 => { 565 | const [s2, a] = this.runWith(s1); 566 | return st(a).runWith(s2); 567 | }); 568 | }, 569 | map(f: (a: A) => B) { 570 | return state(s1 => { 571 | const [s2, a] = this.runWith(s1); 572 | return [s2, f(a)]; 573 | }); 574 | } 575 | }); 576 | ``` 577 | 578 | We have almost an Applicative since we hold a function. But this function has this particular signature: `state => [state, A]`. Now, we return a pair of our state and value. 579 | 580 | You pile up functions and then, in the end, call `runWith` and pass the initial value. 581 | 582 | ```typescript 583 | const initialState = true; 584 | 585 | const state = state(st => [st, 50]); 586 | const process1 = (x: number) => x * 10; 587 | const process2 = (x: number) => 588 | state>(st => 589 | st 590 | ? [st, either(undefined, x + 50)] 591 | : [st, either("error")] 592 | ); 593 | const process3 = (x: number) => x + 10; 594 | 595 | const x = state 596 | .map(process1) 597 | .chain(process2) 598 | .map(e => e.map(process3)) 599 | .runWith(initialState)[1] 600 | .fold( 601 | x => x, 602 | x => x 603 | ); 604 | 605 | // x = 560 (50 -> 500 -> 550 -> 560) 606 | ``` 607 | 608 | In this case, we composed some functions, including a function that returns `Either`. 609 | 610 | It's quite elegant solution. In our example, we did not change the state, but it's possible with the `chain` method. 611 | 612 | ## Reader Monad 613 | 614 | In addition to states, our functions may need configuration. For these cases, we use the Reader Monad. 615 | 616 | The good news is that since we already created the State Monad, we almost have the Reader Monad, with a little gotcha, we need to prevent the config change. 617 | 618 | So our naive implementation would be: 619 | 620 | ```typescript 621 | export interface Reader { 622 | addConfig: (c: C) => [C, A]; 623 | chain: (st: (a: A) => Reader) => Reader; 624 | map: (f: (a: A) => B) => Reader; 625 | } 626 | 627 | export const reader = (f: (c: C) => [C, A]): Reader => ({ 628 | addConfig: (c: C) => f(c), 629 | chain(r: (a: A) => Reader) { 630 | return reader(c => { 631 | const [_, a] = this.addConfig(c); 632 | return r(a).addConfig(c); 633 | }); 634 | }, 635 | map(f: (a: A) => B) { 636 | return reader(c => { 637 | const [_, a] = this.addConfig(c); 638 | return [c, f(a)]; 639 | }); 640 | } 641 | }); 642 | ``` 643 | 644 | As we can see, in the `chain` method, I'm ignoring the config that cames from the first Reader (`[_, a]`), and using the first one. That way, even if the user changes the config by mistake, we stick with the original. 645 | 646 | And, of course, our super contrived example: 647 | 648 | ```typescript 649 | const config = { env: "development" }; 650 | type Config = typeof config; 651 | const withConfig = (c: Config): [Config, number] => [ 652 | c, 653 | c.env === "development" ? 10 : 0 654 | ]; 655 | const f = (x: number) => x * 10; 656 | const g = (x: number) => x - 10; 657 | 658 | reader(withConfig) 659 | .map(f) 660 | .map(g) 661 | .addConfig(config); 662 | 663 | // env === 'development' -> 10 -> 100 -> 90 664 | ``` 665 | 666 | ## Pattern Matching 667 | 668 | I was researching about pattern matching and found an incredible [article](https://medium.com/swlh/pattern-matching-in-typescript-with-record-and-wildcard-patterns-6097dd4e471d) and [lib](https://github.com/WimJongeneel/ts-pattern-matching) written by Wim Jongeneel. 669 | 670 | He uses a lot of TS features that are new to me. Here another cool [article](https://dev.to/aexol/typescript-tutorial-infer-keyword-2cn) from Artur Czemiel explaining the `infer` and conditional types. 671 | 672 | With that, I rewrote the Either to use pattern matching: 673 | 674 | ```typescript 675 | ype EitherValue = { tag: "left"; value: L } | { tag: "right"; value: R }; 676 | 677 | export interface Right { 678 | map: (f: (x: R) => B) => Right; 679 | chain: (f: (x: R) => Right) => Right; 680 | fold: (onLeft: (x: L) => B, onRight: (x: R) => C) => B | C; 681 | inspect: () => string; 682 | } 683 | 684 | export interface Left { 685 | map: (f: (x: R) => B) => Left; 686 | chain: (f: (x: R) => Left) => Left; 687 | fold: (onLeft: (x: L) => B, onRight: (x: R) => C) => B | C; 688 | inspect: () => string; 689 | } 690 | 691 | const right = (value: R): Right => ({ 692 | map: (f: (x: R) => B) => right(f(value)), 693 | chain: (f: (x: R) => Right) => f(value), 694 | fold: (onLeft: (x: L) => B, onRight: (x: R) => C) => onRight(value), 695 | inspect: () => `Right(${value})` 696 | }); 697 | 698 | const left = (value: L): Left => ({ 699 | map: (_: (x: R) => B) => left(value), 700 | chain: (_: (x: R) => Right) => left(value), 701 | fold: (onLeft: (x: L) => B, onRight: (x: R) => C) => onLeft(value), 702 | inspect: () => `Left(${value})` 703 | }); 704 | 705 | export type Either = Right | Left; 706 | 707 | export const either = ( 708 | taggedValue: EitherValue 709 | ): Left | Right => 710 | match, Right | Left>(taggedValue) 711 | .with({ tag: "left" }, ({ tag, value }) => left(value)) 712 | .with({ tag: "right" }, ({ tag, value }) => right(value)) 713 | .run(); 714 | ``` 715 | 716 | There still a ton to TS stuff I need to learn. Conditional typing can improve our sum types, so stay tuned for more refactor soon. 717 | 718 | ## Work in progress 719 | 720 | This repo is a journal of my learning process though the land of Functional Programing and TypeScript. 721 | All help is welcome, so feel free to propose code improvements, fixes, and more material to learn. 722 | 723 | ## Contributing 724 | 725 | 1. Fork it! 726 | 2. Create your feature branch: git checkout -b my-new-feature 727 | 3. Commit your changes: git commit -am 'Add some feature' 728 | 4. Push to the branch: git push origin my-new-feature 729 | 5. Submit a pull request :D 730 | 731 | ## License 732 | 733 | The MIT License (MIT) 734 | 735 | Copyright (c) 2020 Rafael Campos 736 | 737 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 738 | 739 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 740 | 741 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 742 | --------------------------------------------------------------------------------