Skip to content

Commit 89e0387

Browse files
committed
feat(fio): add support for environment access
1 parent e58d43d commit 89e0387

File tree

5 files changed

+123
-76
lines changed

5 files changed

+123
-76
lines changed

src/internals/Evaluate.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const Evaluate = <E, A>(
1515
rej: CB<E>,
1616
res: CB<A>
1717
): void => {
18-
const {stackA, stackE, cancellationList, sh} = context
18+
const {stackA, stackE, stackEnv, cancellationList, sh} = context
1919
let data: unknown
2020

2121
while (true) {
@@ -73,6 +73,20 @@ export const Evaluate = <E, A>(
7373
data = nContext
7474
break
7575

76+
case Tag.Provide:
77+
stackA.push(j.i0)
78+
stackEnv.push(j.i1)
79+
break
80+
81+
case Tag.Environment:
82+
data = stackEnv.pop()
83+
break
84+
85+
case Tag.Access:
86+
const env = stackEnv.pop()
87+
data = j.i0(env)
88+
break
89+
7690
case Tag.Async:
7791
const id = cancellationList.push(
7892
j.i0(

src/internals/FiberContext.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,22 @@ import {Evaluate} from './Evaluate'
1818
*/
1919
export class FiberContext<E = never, A = never> extends Fiber<E, A>
2020
implements ICancellable {
21+
/**
22+
* Pure implementation of cancel()
23+
*/
24+
public get abort(): UIO<void> {
25+
return FIO.uio(() => this.$abort())
26+
}
27+
28+
/**
29+
* Pure implementation of $resume().
30+
*/
31+
public get resume(): FIO<E, A> {
32+
return FIO.asyncIO<E, A>((rej, res) => this.$resume(rej, res))
33+
}
2134
public readonly stackA: Instruction[] = []
2235
public readonly stackE: Array<(e: unknown) => Instruction> = []
36+
public readonly stackEnv: unknown[] = []
2337

2438
public constructor(
2539
public readonly sh: IScheduler,
@@ -68,20 +82,6 @@ export class FiberContext<E = never, A = never> extends Fiber<E, A>
6882
return this
6983
}
7084

71-
/**
72-
* Pure implementation of cancel()
73-
*/
74-
public get abort(): UIO<void> {
75-
return FIO.uio(() => this.$abort())
76-
}
77-
78-
/**
79-
* Pure implementation of $resume().
80-
*/
81-
public get resume(): FIO<E, A> {
82-
return FIO.asyncIO<E, A>((rej, res) => this.$resume(rej, res))
83-
}
84-
8585
public resumeAsync(cb: (exit: Exit<E, A>) => UIO<void>): UIO<Fiber<E, A>> {
8686
const eee = <X>(con: (x: X) => Exit<E, A>) => (data: X) => {
8787
// tslint:disable-next-line: no-use-before-declare

src/main/FIO.ts

Lines changed: 73 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,46 @@ import {Fiber} from './Fiber'
1212
import {Instruction, Tag} from './Instructions'
1313
import {Ref} from './Ref'
1414

15-
//#region Helper Functions
1615
const Id = <A>(_: A): A => _
1716
const ExitRef = <E = never, A = never>() => Ref.of<Exit<E, A>>(Exit.pending)
18-
//#endregion
1917

20-
type AA<A1, A2> = A1 & A2 extends never ? never : [A1, A2]
18+
type iRR<R1, R2> = R1 & R2 extends never ? R1 | R2 : R1 & R2
19+
export type NoEnv = never
20+
type iAA<A1, A2> = A1 & A2 extends never ? never : [A1, A2]
2121

22-
export type Task<A> = FIO<Error, A>
23-
export type UIO<A> = FIO<never, A>
22+
export type IO<E, A> = FIO<E, A>
23+
export type Task<A> = IO<Error, A>
24+
export type UIO<A> = IO<never, A>
2425

25-
export class FIO<E1 = unknown, A1 = unknown> {
26+
export class FIO<E1 = unknown, A1 = unknown, R1 = NoEnv> {
2627
public get asInstruction(): Instruction {
2728
return this as Instruction
2829
}
2930

30-
public get fork(): FIO<never, Fiber<E1, A1>> {
31+
public get fork(): FIO<never, Fiber<E1, A1>, R1> {
3132
return new FIO(Tag.Fork, this)
3233
}
3334

34-
public get once(): FIO<never, FIO<E1, A1>> {
35-
return Await.of<E1, A1>().map(await => await.set(this).and(await.get))
35+
public get env(): FIO<never, R1, R1> {
36+
return FIO.environment<R1>()
3637
}
3738

38-
public get void(): FIO<E1, void> {
39+
public get once(): FIO<never, FIO<E1, A1>, R1> {
40+
return this.env.chain(env =>
41+
Await.of<E1, A1>().map(await =>
42+
await.set(this.provide(env)).and(await.get)
43+
)
44+
)
45+
}
46+
47+
public get void(): FIO<E1, void, R1> {
3948
return this.const(undefined)
4049
}
4150

51+
public static access<R, A>(cb: (R: R) => A): FIO<never, A, R> {
52+
return new FIO(Tag.Access, cb)
53+
}
54+
4255
/**
4356
* **NOTE:** The default type is set to `never` because it hard for typescript to infer the types based on how we use `res`.
4457
* Using `never` will give users compile time error always while using.
@@ -67,17 +80,17 @@ export class FIO<E1 = unknown, A1 = unknown> {
6780
return FIO.async((rej, res, sh) => cb(res, sh))
6881
}
6982

70-
public static catch<E1, A1, E2, A2>(
71-
fa: FIO<E1, A1>,
72-
aFe: (e: E1) => FIO<E2, A2>
73-
): FIO<E2, A2> {
83+
public static catch<E1, A1, R1, E2, A2, R2>(
84+
fa: FIO<E1, A1, R1>,
85+
aFe: (e: E1) => FIO<E2, A2, R2>
86+
): FIO<E2, A2, iRR<R1, R2>> {
7487
return new FIO(Tag.Catch, fa, aFe)
7588
}
7689

77-
public static chain<E1, A1, E2, A2>(
78-
fa: FIO<E1, A1>,
79-
aFb: (a: A1) => FIO<E2, A2>
80-
): FIO<E1 | E2, A2> {
90+
public static chain<E1, A1, R1, E2, A2, R2>(
91+
fa: FIO<E1, A1, R1>,
92+
aFb: (a: A1) => FIO<E2, A2, R2>
93+
): FIO<E1 | E2, A2, iRR<R1, R2>> {
8194
return new FIO(Tag.Chain, fa, aFb)
8295
}
8396

@@ -104,6 +117,16 @@ export class FIO<E1 = unknown, A1 = unknown> {
104117
)
105118
}
106119

120+
public static environment<R1 = never>(): FIO<never, R1, R1> {
121+
return new FIO(Tag.Environment)
122+
}
123+
124+
public static flatten<E1, A1, R1, E2, A2, R2>(
125+
fio: FIO<E1, FIO<E2, A2, R2>, R1>
126+
): FIO<E1 | E2, A2, iRR<R1, R2>> {
127+
return fio.chain(Id)
128+
}
129+
107130
public static fromExit<E, A>(exit: Exit<E, A>): FIO<E, A> {
108131
return Exit.isSuccess(exit)
109132
? FIO.of(exit[1])
@@ -116,10 +139,10 @@ export class FIO<E1 = unknown, A1 = unknown> {
116139
return FIO.resume(cb)
117140
}
118141

119-
public static map<E1, A1, A2>(
120-
fa: FIO<E1, A1>,
142+
public static map<E1, A1, R1, A2>(
143+
fa: FIO<E1, A1, R1>,
121144
ab: (a: A1) => A2
122-
): FIO<E1, A2> {
145+
): FIO<E1, A2, R1> {
123146
return new FIO(Tag.Map, fa, ab)
124147
}
125148

@@ -180,35 +203,40 @@ export class FIO<E1 = unknown, A1 = unknown> {
180203
public readonly i1?: unknown
181204
) {}
182205

183-
public and<E2, A2>(aFb: FIO<E2, A2>): FIO<E1 | E2, A2> {
206+
public and<E2, A2, R2>(aFb: FIO<E2, A2, R2>): FIO<E1 | E2, A2, iRR<R1, R2>> {
184207
return this.chain(() => aFb)
185208
}
186209

187-
public catch<E2, A2>(aFb: (e: E1) => FIO<E2, A2>): FIO<E2, A1 | A2> {
210+
public catch<E2, A2, R2>(
211+
aFb: (e: E1) => FIO<E2, A2, R2>
212+
): FIO<E2, A1 | A2, iRR<R1, R2>> {
188213
return FIO.catch(this, aFb)
189214
}
190215

191-
public chain<E2, A2>(aFb: (a: A1) => FIO<E2, A2>): FIO<E1 | E2, A2> {
216+
public chain<E2, A2, R2>(
217+
aFb: (a: A1) => FIO<E2, A2, R2>
218+
): FIO<E1 | E2, A2, iRR<R1, R2>> {
192219
return FIO.chain(this, aFb)
193220
}
194221

195-
public const<A2>(a: A2): FIO<E1, A2> {
222+
public const<A2>(a: A2): FIO<E1, A2, R1> {
196223
return this.and(FIO.of(a))
197224
}
198225

199-
public delay(duration: number): FIO<E1, A1> {
226+
public delay(duration: number): FIO<E1, A1, R1> {
200227
return FIO.timeout(this, duration).chain(Id)
201228
}
202229

203-
public map<A2>(ab: (a: A1) => A2): FIO<E1, A2> {
230+
public map<A2>(ab: (a: A1) => A2): FIO<E1, A2, R1> {
204231
return FIO.map(this, ab)
205232
}
233+
public provide = (r1: R1): FIO<E1, A1> => new FIO(Tag.Provide, this, r1)
206234

207-
public raceWith<E2, A2>(
208-
that: FIO<E2, A2>,
235+
public raceWith<E2, A2, R2>(
236+
that: FIO<E2, A2, R2>,
209237
cb1: (exit: Exit<E1, A1>, fiber: Fiber<E2, A2>) => UIO<void>,
210238
cb2: (exit: Exit<E2, A2>, fiber: Fiber<E1, A1>) => UIO<void>
211-
): FIO<never, void> {
239+
): FIO<never, void, iRR<R1, R2>> {
212240
return this.fork.zip(that.fork).chain(([f1, f2]) => {
213241
const resume1 = f1.resumeAsync(exit => cb1(exit, f2))
214242
const resume2 = f2.resumeAsync(exit => cb2(exit, f1))
@@ -217,25 +245,31 @@ export class FIO<E1 = unknown, A1 = unknown> {
217245
})
218246
}
219247

220-
public tap(io: UIO<void>): FIO<E1, A1> {
248+
public tap(io: UIO<void>): FIO<E1, A1, R1> {
221249
return this.chain(_ => io.const(_))
222250
}
223251

224-
public zip<E2, A2>(that: FIO<E2, A2>): FIO<E1 | E2, AA<A1, A2>> {
225-
return this.zipWith(that, (a, b) => [a, b]) as FIO<E1 | E2, AA<A1, A2>>
252+
public zip<E2, A2, R2>(
253+
that: FIO<E2, A2, R2>
254+
): FIO<E1 | E2, iAA<A1, A2>, iRR<R1, R2>> {
255+
return this.zipWith(that, (a, b) => [a, b]) as FIO<
256+
E1 | E2,
257+
iAA<A1, A2>,
258+
iRR<R1, R2>
259+
>
226260
}
227261

228-
public zipWith<E2, A2, C>(
229-
that: FIO<E2, A2>,
262+
public zipWith<E2, A2, R2, C>(
263+
that: FIO<E2, A2, R2>,
230264
c: (a1: A1, a2: A2) => C
231-
): FIO<E1 | E2, C> {
265+
): FIO<E1 | E2, C, iRR<R1, R2>> {
232266
return this.chain(a1 => that.map(a2 => c(a1, a2)))
233267
}
234268

235-
public zipWithPar<E2, A2, C>(
236-
that: FIO<E2, A2>,
269+
public zipWithPar<E2, A2, R2, C>(
270+
that: FIO<E2, A2, R2>,
237271
c: (e1: Exit<E1, A1>, e2: Exit<E2, A2>) => C
238-
): FIO<void, C> {
272+
): FIO<void, C, iRR<R1, R2>> {
239273
// Create Caches
240274
const cache = ExitRef<E1, A1>().zip(ExitRef<E2, A2>())
241275

src/main/Instructions.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ export enum Tag {
1111
Catch,
1212
Chain,
1313
Constant,
14+
Environment,
15+
Fork,
1416
Map,
1517
Never,
18+
Provide,
1619
Reject,
17-
Fork,
1820
Try,
1921
TryM
2022
}
@@ -65,6 +67,15 @@ export interface IAccess<X = unknown, Y = unknown> {
6567
tag: Tag.Access
6668
i0(X: X): Y
6769
}
70+
export interface IProvide<R = unknown> {
71+
i0: Instruction
72+
i1: R
73+
tag: Tag.Provide
74+
}
75+
76+
export interface IEnvironment {
77+
tag: Tag.Environment
78+
}
6879

6980
/**
7081
* @ignore
@@ -75,9 +86,11 @@ export type Instruction =
7586
| ICatch
7687
| IChain
7788
| IConstant
89+
| IEnvironment
7890
| IFork
7991
| IMap
8092
| INever
93+
| IProvide
8194
| IReject
8295
| ITry
8396
| ITryM

test/FIO.test.ts

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,13 @@ describe('FIO', () => {
3131
})
3232

3333
describe('access', () => {
34-
it.skip('should access a value and transform')
35-
})
36-
37-
describe('accessM', () => {
38-
it.skip('should purely access the env')
39-
})
40-
41-
describe('chain', () => {
42-
it.skip('should sequence the operations')
34+
it('should access a value and transform', () => {
35+
const actual = testRuntime().executeSync(
36+
FIO.access((name: string) => name.length).provide('FIO')
37+
)
38+
const expected = 3
39+
assert.strictEqual(actual, expected)
40+
})
4341
})
4442

4543
describe('reject', () => {
@@ -75,10 +73,6 @@ describe('FIO', () => {
7573
})
7674
})
7775

78-
describe('accessP', () => {
79-
it.skip('should access promise based envs')
80-
})
81-
8276
describe('try', () => {
8377
it('should call the cb function', () => {
8478
let i = 1000
@@ -299,8 +293,6 @@ describe('FIO', () => {
299293
assert.strictEqual(runtime.scheduler.now(), 1101)
300294
})
301295

302-
it.skip('should bubble the env')
303-
304296
it('should resolve after the IO is completed', () => {
305297
const counter = new Counter()
306298
const runtime = testRuntime()
@@ -409,12 +401,6 @@ describe('FIO', () => {
409401
})
410402
})
411403

412-
describe('provide', () => {
413-
it.skip('should provide the env to FIO')
414-
415-
it.skip('should maintain order')
416-
})
417-
418404
describe('zipWith', () => {
419405
it('should sequentially combine two FIO', () => {
420406
const actual = testRuntime().executeSync(

0 commit comments

Comments
 (0)