This repository has been archived by the owner on Jan 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from snatvb/develop
New monad - IO [#2]
- Loading branch information
Showing
10 changed files
with
308 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# IO | ||
|
||
The monad need to _hide_ some side effect and keep your code clean. | ||
|
||
You can use it for working with `WebSocket`, `fetch`, `console.log`, `DOM` and etc. | ||
What could doing some side effect. | ||
|
||
```ts | ||
import { Either, IO } from 'monad-maniac' | ||
|
||
type SideEffectDataType = { [id: number]: string } | ||
let SideEffectData: SideEffectDataType = { | ||
1: 'Jake', | ||
2: 'Bob', | ||
3: 'Alice', | ||
} | ||
|
||
const logError = (...args: any[]) => console.error('Got error:', ...args) | ||
|
||
const readName = (id: number) => (): Either.Shape<string, string> => { | ||
const name = SideEffectData[id] | ||
return name | ||
? Either.right(name) | ||
: Either.left('Name not found') | ||
} | ||
|
||
const writeName = (id: number) => (name: Either.Shape<string, string>) => { | ||
return name.caseOf({ | ||
Left: (error) => { | ||
logError(error) | ||
return error | ||
}, | ||
Right: (name) => SideEffectData[id] = name, | ||
}) | ||
} | ||
|
||
const addFired = (name: string) => `${name} was fired!` | ||
|
||
// Will get `Jake` | ||
// Changed string to `Jake was fired!` | ||
// Write the string to SideEffectData | ||
// In result will be `Jake was fired!` | ||
const result = IO | ||
.from(readName(1)) | ||
.map((name) => name.map(addFired)) | ||
.chain(writeName(1)) | ||
|
||
// Name not found | ||
// Also will show error in console | ||
const resultFailure = IO | ||
.from(readName(10)) | ||
.map((name) => name.map(addFired)) | ||
.chain(writeName(10)) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
type ApplicativeResult<T, U extends ((value: T) => any)> = Applicative<ReturnType<U>> | ||
|
||
export interface Functor<T> { | ||
map<U>(fn: (value: T) => U): Functor<U> | ||
} | ||
|
||
export interface Applicative<T> { | ||
apply<U extends ((value: T) => any)>(functor: Functor<U>): ApplicativeResult<T, U> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/** [[include:doc/io.md]] */ | ||
|
||
/** Fix tsdoc */ | ||
import { Functor } from './interfaces' | ||
import * as helpers from './helpers' | ||
|
||
export class IO<T extends (...args: any[]) => any> implements Functor<T> { | ||
private effect: T | ||
|
||
constructor(effect: T) { | ||
this.effect = effect | ||
} | ||
|
||
/** | ||
* This method running function from `IO` and result will be referred to | ||
* `fn` function. Result from `fn` function will be wrapped up into `IO`. | ||
*/ | ||
map<U>(fn: (value: ReturnType<T>) => U): IO<() => U> { | ||
return new IO(() => fn(this.effect())) | ||
} | ||
|
||
/** | ||
* Same like `map`, but... | ||
* Result from `fn` function will **not** be wrapped up into `IO`. | ||
*/ | ||
chain<U>(fn: (value: ReturnType<T>) => U): U { | ||
return fn(this.effect()) | ||
} | ||
|
||
/** To run function from `IO` */ | ||
run(): ReturnType<T> { | ||
return this.effect() | ||
} | ||
|
||
/** Just returns `IO` as string */ | ||
toString(): string { | ||
return 'IO' | ||
} | ||
|
||
} | ||
|
||
/** This need just use with context `IO.Shape<T>` instead of `IO.IO<T>` */ | ||
export interface Shape<T extends (...args: any[]) => any> extends IO<T> {} | ||
|
||
/** | ||
* This function are getting some value and contains it | ||
* in function where the function will returns the value (`() => value` ) | ||
* */ | ||
export function of<T>(value: T) { | ||
return new IO(() => value) | ||
} | ||
|
||
/** Making `IO` monad from function */ | ||
export function from(fn: (...args: any[]) => any) { | ||
return new IO(fn) | ||
} | ||
|
||
/** Calling method `run` from `IO` instance */ | ||
export function run<T extends (...args: any[]) => any>(io: IO<T>): ReturnType<T> { | ||
return io.run() | ||
} | ||
|
||
/** Calling method `toString` from `IO` instance */ | ||
export function toString<T extends (...args: any[]) => any>(io: IO<T>): string { | ||
return io.toString() | ||
} | ||
|
||
/** | ||
* Method like [`IO.map`](../interfaces/_io_.io.html#map) | ||
* but to get `IO` and call method `map` with a function. | ||
* | ||
* */ | ||
export function map<T extends (...args: any[]) => any, U>(fn: (value: ReturnType<T>) => U, io: IO<T>): IO<() => U> | ||
/** | ||
* Just curried `map`. | ||
* | ||
* _(a -> b) -> IO(a) -> IO(b)_ | ||
*/ | ||
export function map<T extends (...args: any[]) => any, U>(fn: (value: ReturnType<T>) => U): (io: IO<T>) => IO<() => U> | ||
export function map<T extends (...args: any[]) => any, U>(fn: (value: ReturnType<T>) => U, io?: IO<T>): IO<() => U> | ((io: IO<T>) => IO<() => U>) { | ||
const op = (functor: IO<T>) => functor.map(fn) | ||
return helpers.curry1(op, io) | ||
} | ||
|
||
/** | ||
* Method like [`IO.chain`](../interfaces/_io_.io.html#chain) | ||
* but to get `IO` and call method `chain` with a function. | ||
* | ||
* */ | ||
export function chain<T extends (...args: any[]) => any, U>(fn: (value: ReturnType<T>) => U, io: IO<T>): U | ||
/** | ||
* Just curried `chain`. | ||
* | ||
* _(a -> b) -> IO(a) -> b_ | ||
*/ | ||
export function chain<T extends (...args: any[]) => any, U>(fn: (value: ReturnType<T>) => U): (io: IO<T>) => U | ||
export function chain<T extends (...args: any[]) => any, U>(fn: (value: ReturnType<T>) => U, io?: IO<T>): U | ((io: IO<T>) => U) { | ||
const op = (functor: IO<T>) => functor.chain(fn) | ||
return helpers.curry1(op, io) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { Either, IO } from '../src' | ||
|
||
describe('Pure functions', () => { | ||
it('of', () => { | ||
expect(IO.of(222).run()).toBe(222) | ||
expect(IO.of(4).map((x) => x * x).run()).toBe(16) | ||
}) | ||
|
||
it('from', () => { | ||
expect(IO.from(() => 222).run()).toBe(222) | ||
expect(IO.from(() => 4).map((x) => x * x).run()).toBe(16) | ||
}) | ||
|
||
it('run', () => { | ||
const io = IO.from(() => 222) | ||
expect(IO.run(io)).toBe(222) | ||
}) | ||
|
||
it('toString', () => { | ||
const io = IO.from(() => 222) | ||
expect(IO.toString(io)).toBe('IO') | ||
}) | ||
}) | ||
|
||
describe('Docs', () => { | ||
it('map and chain', () => { | ||
type SideEffectDataType = { [id: number]: string } | ||
let SideEffectData: SideEffectDataType = { | ||
1: 'Jake', | ||
2: 'Bob', | ||
3: 'Alice', | ||
} | ||
|
||
const logError = (...args: any[]) => args | ||
|
||
const readName = (id: number) => (): Either.Shape<string, string> => { | ||
const name = SideEffectData[id] | ||
return name | ||
? Either.right(name) | ||
: Either.left('Name not found') | ||
} | ||
|
||
const writeName = (id: number) => (name: Either.Shape<string, string>) => { | ||
return name.caseOf({ | ||
Left: (error) => { | ||
logError(error) | ||
return error | ||
}, | ||
Right: (name) => SideEffectData[id] = name, | ||
}) | ||
} | ||
|
||
const addFired = (name: string) => `${name} was fired!` | ||
|
||
const result = IO | ||
.from(readName(1)) | ||
.map((name) => name.map(addFired)) | ||
.chain(writeName(1)) | ||
|
||
const resultFailure = IO | ||
.from(readName(10)) | ||
.map((name) => name.map(addFired)) | ||
.chain(writeName(10)) | ||
|
||
expect(result).toBe('Jake was fired!') | ||
expect(resultFailure).toBe('Name not found') | ||
expect(SideEffectData).toEqual({ | ||
1: 'Jake was fired!', | ||
2: 'Bob', | ||
3: 'Alice', | ||
}) | ||
}) | ||
}) | ||
|
||
describe('Pure functions', () => { | ||
type SideEffectDataType = { [id: number]: string } | ||
let SideEffectData: SideEffectDataType = { | ||
1: 'Jake', | ||
2: 'Bob', | ||
3: 'Alice', | ||
} | ||
|
||
const logError = (...args: any[]) => args | ||
|
||
const readName = (id: number) => (): Either.Shape<string, string> => { | ||
const name = SideEffectData[id] | ||
return name | ||
? Either.right(name) | ||
: Either.left('Name not found') | ||
} | ||
|
||
const writeName = (id: number) => (name: Either.Shape<string, string>) => { | ||
return name.caseOf({ | ||
Left: (error) => { | ||
logError(error) | ||
return error | ||
}, | ||
Right: (name) => SideEffectData[id] = name, | ||
}) | ||
} | ||
it('map and chain', () => { | ||
const addFired = (name: string) => `${name} was fired!` | ||
|
||
const readedIO = IO.from(readName(1)) | ||
|
||
const resultAddedFired = IO.map((name) => name.map(addFired), readedIO) | ||
const result = IO.chain(writeName(1), resultAddedFired) | ||
|
||
expect(readedIO.toString()).toBe('IO') | ||
expect(result).toBe('Jake was fired!') | ||
expect(SideEffectData).toEqual({ | ||
1: 'Jake was fired!', | ||
2: 'Bob', | ||
3: 'Alice', | ||
}) | ||
}) | ||
}) |