An educational pure functional programming library in TypeScript
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 320 lines 8.8 kB view raw
1import { describe, expect, it } from "bun:test" 2import { 3 apOption, 4 apResult, 5 err, 6 liftA2Option, 7 liftA2Result, 8 none, 9 ok, 10 optionInstances, 11 pipe, 12 resultInstances, 13 some, 14} from "../src/index" 15 16describe("Type Classes", () => { 17 describe("apResult", () => { 18 it("applies function in Ok to value in Ok", () => { 19 const rf = ok((x: number) => x * 2) 20 const ra = ok(10) 21 const result = pipe(rf, apResult(ra)) 22 expect(result).toEqual(ok(20)) 23 }) 24 25 it("returns Err when function is Err", () => { 26 const rf = err("no function") 27 const ra = ok(10) 28 const result = pipe(rf, apResult(ra)) 29 expect(result).toEqual(err("no function")) 30 }) 31 32 it("returns Err when value is Err", () => { 33 const rf = ok((x: number) => x * 2) 34 const ra = err("no value") 35 const result = pipe(rf, apResult(ra)) 36 expect(result).toEqual(err("no value")) 37 }) 38 39 it("returns first Err when both are Err (short-circuit)", () => { 40 const rf = err("func error") 41 const ra = err("value error") 42 const result = pipe(rf, apResult(ra)) 43 expect(result).toEqual(err("func error")) 44 }) 45 46 it("works with curried functions", () => { 47 const add = (a: number) => (b: number) => a + b 48 const result = pipe(ok(add), apResult(ok(10)), apResult(ok(5))) 49 expect(result).toEqual(ok(15)) 50 }) 51 }) 52 53 describe("apOption", () => { 54 it("applies function in Some to value in Some", () => { 55 const of_ = some((x: number) => x * 2) 56 const oa = some(10) 57 const result = pipe(of_, apOption(oa)) 58 expect(result).toEqual(some(20)) 59 }) 60 61 it("returns None when function is None", () => { 62 const of_ = none as typeof none & { _tag: "None" } 63 const oa = some(10) 64 const result = pipe(of_, apOption(oa)) 65 expect(result._tag).toBe("None") 66 }) 67 68 it("returns None when value is None", () => { 69 const of_ = some((x: number) => x * 2) 70 const oa = none 71 const result = pipe(of_, apOption(oa)) 72 expect(result._tag).toBe("None") 73 }) 74 75 it("works with curried functions", () => { 76 const add = (a: number) => (b: number) => a + b 77 const result = pipe(some(add), apOption(some(10)), apOption(some(5))) 78 expect(result).toEqual(some(15)) 79 }) 80 }) 81 82 describe("liftA2Result", () => { 83 it("lifts binary function to work with Results", () => { 84 const add = (a: number, b: number) => a + b 85 const addResults = liftA2Result(add) 86 87 expect(addResults(ok(1), ok(2))).toEqual(ok(3)) 88 }) 89 90 it("returns Err when first is Err", () => { 91 const add = (a: number, b: number) => a + b 92 const addResults = liftA2Result(add) 93 94 expect(addResults(err("first"), ok(2))).toEqual(err("first")) 95 }) 96 97 it("returns Err when second is Err", () => { 98 const add = (a: number, b: number) => a + b 99 const addResults = liftA2Result(add) 100 101 expect(addResults(ok(1), err("second"))).toEqual(err("second")) 102 }) 103 104 it("returns first Err when both are Err", () => { 105 const add = (a: number, b: number) => a + b 106 const addResults = liftA2Result(add) 107 108 expect(addResults(err("first"), err("second"))).toEqual(err("first")) 109 }) 110 }) 111 112 describe("liftA2Option", () => { 113 it("lifts binary function to work with Options", () => { 114 const add = (a: number, b: number) => a + b 115 const addOptions = liftA2Option(add) 116 117 expect(addOptions(some(1), some(2))).toEqual(some(3)) 118 }) 119 120 it("returns None when first is None", () => { 121 const add = (a: number, b: number) => a + b 122 const addOptions = liftA2Option(add) 123 124 expect(addOptions(none, some(2))._tag).toBe("None") 125 }) 126 127 it("returns None when second is None", () => { 128 const add = (a: number, b: number) => a + b 129 const addOptions = liftA2Option(add) 130 131 expect(addOptions(some(1), none)._tag).toBe("None") 132 }) 133 }) 134 135 describe("resultInstances", () => { 136 it("has map function", () => { 137 const result = pipe( 138 ok(5), 139 resultInstances.map((x) => x * 2), 140 ) 141 expect(result).toEqual(ok(10)) 142 }) 143 144 it("has of function (alias for ok)", () => { 145 expect(resultInstances.of(42)).toEqual(ok(42)) 146 }) 147 148 it("has ap function", () => { 149 const result = pipe( 150 ok((x: number) => x + 1), 151 resultInstances.ap(ok(5)), 152 ) 153 expect(result).toEqual(ok(6)) 154 }) 155 156 it("has flatMap function", () => { 157 const result = pipe( 158 ok(5), 159 resultInstances.flatMap((x) => ok(x * 2)), 160 ) 161 expect(result).toEqual(ok(10)) 162 }) 163 164 it("has bimap function", () => { 165 const result = pipe( 166 ok(5), 167 resultInstances.bimap( 168 (x) => x * 2, 169 (e: string) => e.toUpperCase(), 170 ), 171 ) 172 expect(result).toEqual(ok(10)) 173 }) 174 }) 175 176 describe("optionInstances", () => { 177 it("has map function", () => { 178 const result = pipe( 179 some(5), 180 optionInstances.map((x) => x * 2), 181 ) 182 expect(result).toEqual(some(10)) 183 }) 184 185 it("has of function (alias for some)", () => { 186 expect(optionInstances.of(42)).toEqual(some(42)) 187 }) 188 189 it("has ap function", () => { 190 const result = pipe( 191 some((x: number) => x + 1), 192 optionInstances.ap(some(5)), 193 ) 194 expect(result).toEqual(some(6)) 195 }) 196 197 it("has flatMap function", () => { 198 const result = pipe( 199 some(5), 200 optionInstances.flatMap((x) => some(x * 2)), 201 ) 202 expect(result).toEqual(some(10)) 203 }) 204 }) 205 206 describe("Functor laws (Result)", () => { 207 it("identity: map(id) === id", () => { 208 const id = <T>(x: T) => x 209 const original = ok(42) 210 expect(pipe(original, resultInstances.map(id))).toEqual(original) 211 }) 212 213 it("composition: map(f . g) === map(f) . map(g)", () => { 214 const f = (x: number) => x * 2 215 const g = (x: number) => x + 1 216 const fg = (x: number) => f(g(x)) 217 218 const original = ok(5) 219 const left = pipe(original, resultInstances.map(fg)) 220 const right = pipe( 221 original, 222 resultInstances.map(g), 223 resultInstances.map(f), 224 ) 225 226 expect(left).toEqual(right) 227 }) 228 }) 229 230 describe("liftA2Option (both-None)", () => { 231 it("returns None when both are None", () => { 232 const add = (a: number, b: number) => a + b 233 const addOptions = liftA2Option(add) 234 expect(addOptions(none, none)._tag).toBe("None") 235 }) 236 }) 237 238 describe("Monad laws (Result)", () => { 239 it("left identity: flatMap(f)(of(a)) === f(a)", () => { 240 const f = (x: number) => ok(x * 2) 241 const a = 5 242 243 const left = pipe(resultInstances.of(a), resultInstances.flatMap(f)) 244 const right = f(a) 245 246 expect(left).toEqual(right) 247 }) 248 249 it("right identity: flatMap(of)(m) === m", () => { 250 const m = ok(42) 251 252 const result = pipe(m, resultInstances.flatMap(resultInstances.of)) 253 254 expect(result).toEqual(m) 255 }) 256 }) 257 258 describe("Functor laws (Option)", () => { 259 it("identity: map(id) === id", () => { 260 const id = <T>(x: T) => x 261 const original = some(42) 262 expect(pipe(original, optionInstances.map(id))).toEqual(original) 263 }) 264 265 it("identity on None: map(id) === id", () => { 266 const id = <T>(x: T) => x 267 expect(pipe(none, optionInstances.map(id))).toEqual(none) 268 }) 269 270 it("composition: map(f . g) === map(f) . map(g)", () => { 271 const f = (x: number) => x * 2 272 const g = (x: number) => x + 1 273 const fg = (x: number) => f(g(x)) 274 275 const original = some(5) 276 const left = pipe(original, optionInstances.map(fg)) 277 const right = pipe( 278 original, 279 optionInstances.map(g), 280 optionInstances.map(f), 281 ) 282 283 expect(left).toEqual(right) 284 }) 285 }) 286 287 describe("Monad laws (Option)", () => { 288 it("left identity: flatMap(f)(of(a)) === f(a)", () => { 289 const f = (x: number) => some(x * 2) 290 const a = 5 291 292 const left = pipe(optionInstances.of(a), optionInstances.flatMap(f)) 293 const right = f(a) 294 295 expect(left).toEqual(right) 296 }) 297 298 it("right identity: flatMap(of)(m) === m", () => { 299 const m = some(42) 300 301 const result = pipe(m, optionInstances.flatMap(optionInstances.of)) 302 303 expect(result).toEqual(m) 304 }) 305 306 it("associativity: flatMap(g)(flatMap(f)(m)) === flatMap(x => flatMap(g)(f(x)))(m)", () => { 307 const f = (x: number) => some(x * 2) 308 const g = (x: number) => some(x + 1) 309 310 const m = some(5) 311 const left = pipe(m, optionInstances.flatMap(f), optionInstances.flatMap(g)) 312 const right = pipe( 313 m, 314 optionInstances.flatMap((x) => pipe(f(x), optionInstances.flatMap(g))), 315 ) 316 317 expect(left).toEqual(right) 318 }) 319 }) 320})