Skip to content

Commit 73f4a1a

Browse files
feat: add State monad (#164)
1 parent 2a6fa9c commit 73f4a1a

7 files changed

+325
-10
lines changed

package-lock.json

+240-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"@typescript-eslint/eslint-plugin": "^4.22.0",
5151
"@typescript-eslint/parser": "^4.22.0",
5252
"codecov": "^3.8.1",
53-
"eslint": "^7.24.0",
53+
"eslint": "^7.25.0",
5454
"eslint-plugin-promise": "^5.1.0",
5555
"eslint-plugin-rxjs": "3.1.5",
5656
"fast-check": "^2.14.0",
@@ -75,7 +75,8 @@
7575
"src/**/*.ts"
7676
],
7777
"coverageReporters": [
78-
"lcov"
78+
"lcov",
79+
"text"
7980
],
8081
"coverageThreshold": {
8182
"global": {

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from './either/public_api'
44
export * from './result/public_api'
55
export * from './monad/public_api'
66
export * from './list/public_api'
7+
export * from './state/public_api'

src/state/pubcli_api.spec.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { State } from './public_api'
2+
3+
describe('state api', () => {
4+
it('should export', () => {
5+
expect(new State(a => [a, ''])).toBeInstanceOf(State)
6+
})
7+
})

src/state/public_api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './state'

src/state/state.spec.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { State } from './state'
2+
3+
describe(State.name, () => {
4+
5+
it('should construct', () => {
6+
const sut = new State<string, string>(state => [state, state + '_test'])
7+
.run('starting state')
8+
9+
expect(sut.state).toEqual('starting state')
10+
expect(sut.value).toEqual('starting state_test')
11+
})
12+
13+
it('should of', () => {
14+
const sut = new State<string, string>(state => [state, state + '_test'])
15+
.of(state => [state, 'other'])
16+
.run('starting state')
17+
18+
expect(sut.state).toEqual('starting state')
19+
expect(sut.value).toEqual('other')
20+
})
21+
22+
it('should map', () => {
23+
const sut = new State<string, string>(state => [state, state + '_phase1_'])
24+
.map<number>(pair => [pair.state + '_ran_x1_3', 3])
25+
.run('start_str')
26+
27+
expect(sut.state).toEqual('start_str_ran_x1_3')
28+
expect(sut.value).toEqual(3)
29+
})
30+
31+
it('should flat map', () => {
32+
const sut = new State<string, string>(state => [state, 'v1'])
33+
.flatMap(pair => new State<string, string>(state => [pair.state + state, pair.value]))
34+
.run('start')
35+
36+
expect(sut.state).toEqual('startstart')
37+
expect(sut.value).toEqual('v1')
38+
})
39+
40+
})

src/state/state.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
export type StateTupple<TState, TValue> = [TState, TValue]
3+
4+
export class StatePair<TState, TValue> {
5+
constructor(public readonly state: TState, public readonly value: TValue) { }
6+
}
7+
8+
export class State<TState, TValue> {
9+
10+
constructor(private readonly fn: (state: TState) => StateTupple<TState, TValue>) { }
11+
12+
public of(fn: (state: TState) => StateTupple<TState, TValue>): State<TState, TValue> {
13+
return new State(fn)
14+
}
15+
16+
public map<TValueB>(fn: (state: StatePair<TState, TValue>) => StateTupple<TState, TValueB>): State<TState, TValueB> {
17+
return new State<TState, TValueB>(c => fn(this.run(c)))
18+
}
19+
20+
public flatMap<TValueB>(fn: (state: StatePair<TState, TValue>) => State<TState, TValueB>): State<TState, TValueB> {
21+
return new State<TState, TValueB>(c => {
22+
const pair = fn(this.run(c)).run(c)
23+
return [pair.state, pair.value]
24+
})
25+
}
26+
27+
public run(config: TState): StatePair<TState, TValue> {
28+
const tupple = this.fn(config)
29+
30+
return new StatePair(tupple[0], tupple[1])
31+
}
32+
33+
}

0 commit comments

Comments
 (0)