From 2075cefa4e3ac5f413ef465288c7e23f972b2af6 Mon Sep 17 00:00:00 2001 From: Illia Kornyk Date: Thu, 12 Jun 2025 14:52:45 +0300 Subject: [PATCH 1/2] feat: rewrite IO and Monad classes into Typescript --- JavaScript/io.ts | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 JavaScript/io.ts diff --git a/JavaScript/io.ts b/JavaScript/io.ts new file mode 100644 index 0000000..d02b42c --- /dev/null +++ b/JavaScript/io.ts @@ -0,0 +1,78 @@ +'use strict'; +export {}; + +class IO { + private readonly effect: () => T; + + private constructor(effect: () => T) { + this.effect = effect; + } + + static of(effect: () => T): IO { + return new IO(effect); + } + + map(fn: (value: T) => U): IO { + return new IO(() => fn(this.effect())); + } + + chain(fn: (value: T) => IO): IO { + return new IO(() => fn(this.effect()).run()); + } + + run(): T { + return this.effect(); + } +} + +class Monad { + private readonly value: T; + + private constructor(value: T) { + this.value = value; + } + + static of(value: T): Monad { + return new Monad(value); + } + + map(fn: (value: T) => U): Monad { + const v = structuredClone(this.value) as T; + return Monad.of(fn(v)); + } + + chain(fn: (value: T) => R): R { + const v = structuredClone(this.value) as T; + return fn(v); + } + + ap(this: Monad<(arg: A) => B>, container: Monad): Monad { + return container.map(this.value); + } +} + +type Point = { x: number; y: number }; + +const move = + (d: Point) => + (p: Point): Point => ({ x: p.x + d.x, y: p.y + d.y }); + +const clone = ({ x, y }: Point): Point => ({ x, y }); + +const toString = ({ x, y }: Point): IO => IO.of(() => `(${x}, ${y})`); + +// Usage + +const input = IO.of(() => Monad.of({ x: 10, y: 20 })); + +input + .chain((monad) => monad.chain(toString)) + .map(console.log) + .run(); + +const c0 = input.chain((m) => IO.of(() => m.map(clone))); +const c1 = c0.chain((m) => IO.of(() => Monad.of(move({ x: -5, y: 10 })).ap(m))); + +c1.chain((m) => m.chain(toString)) + .map(console.log) + .run(); From 3117e9565331967b4ea7b6dc85108736bafe2412 Mon Sep 17 00:00:00 2001 From: Illia Kornyk Date: Thu, 12 Jun 2025 15:15:54 +0300 Subject: [PATCH 2/2] feat: alternative implementation --- JavaScript/io-alt.ts | 87 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 JavaScript/io-alt.ts diff --git a/JavaScript/io-alt.ts b/JavaScript/io-alt.ts new file mode 100644 index 0000000..5ceec19 --- /dev/null +++ b/JavaScript/io-alt.ts @@ -0,0 +1,87 @@ +'use strict'; +export {}; + +class Effect { + private readonly command: () => T; + + private constructor(command: () => T) { + this.command = command; + } + + static from(command: () => T): Effect { + return new Effect(command); + } + + transform(fn: (value: T) => U): Effect { + return new Effect(() => fn(this.command())); + } + + chain(fn: (value: T) => Effect): Effect { + return new Effect(() => fn(this.command()).execute()); + } + + execute(): T { + return this.command(); + } +} + +class ValueBox { + private readonly value: T; + + private constructor(value: T) { + this.value = value; + } + + static from(value: T): ValueBox { + return new ValueBox(value); + } + + transform(fn: (v: T) => U): ValueBox { + return new ValueBox(fn(this.clone())); + } + + chain(fn: (v: T) => R): R { + return fn(this.clone()); + } + + apply(this: ValueBox<(arg: A) => B>, other: ValueBox): ValueBox { + return other.transform(this.value); + } + + unwrap(): T { + return this.clone(); + } + + private clone(): T { + return structuredClone(this.value); + } +} + +interface Point { + x: number; + y: number; +} + +const move = + (d: Point) => + (p: Point): Point => ({ x: p.x + d.x, y: p.y + d.y }); +const clone = ({ x, y }: Point): Point => ({ x, y }); +const toString = ({ x, y }: Point) => Effect.from(() => `(${x}, ${y})`); + +// Usage + +const start = Effect.from(() => ValueBox.from({ x: 10, y: 20 })); + +start + .chain((box) => box.chain(toString)) + .transform(console.log) + .execute(); +const c0 = start.chain((b) => Effect.from(() => b.transform(clone))); + +const c1 = c0.chain((b) => + Effect.from(() => ValueBox.from(move({ x: -5, y: 10 })).apply(b)), +); + +c1.chain((b) => b.chain(toString)) + .transform(console.log) + .execute();