Play with Environment
There are many ways you can access and provide environment

Environment

Following the list of environment related combinators:
1
// access all environment
2
export function accessEnvironment<R>(): Effect<R, NoErr, R>
3
4
// access environment and use it effectfully
5
export function accessM<R, R2, E, A>(
6
f: FunctionN<[R], Effect<R2, E, A>>
7
): Effect<R & R2, E, A>
8
9
// access environment and use it purely
10
export function access<R, A, E = NoErr>(
11
f: FunctionN<[R], A>
12
): Effect<R, E, A>
13
14
// merge environments (use deep-merge, use only at the top level)
15
export function mergeEnv<A>(a: A): <B>(b: B) => A & B
16
17
// provide part of the environment (use deep-merge, use only at the top level)
18
export const provide = <R>(r: R) => <R2, E, A>(
19
ma: Effect<R2 & R, E, A>
20
): Effect<R2, E, A> => accessM((r2: R2) => provideAll(mergeEnv(r2)(r))(ma));
21
22
// provide part of the environment with a transformation function
23
// equivalent to provide but efficient (no use of deep merging)
24
export const provideR = <R2, R>(f: (r2: R2) => R) => <E, A>
25
(ma: Effect<R, E, A>): Effect<R2, E, A>
26
27
// provide all the environment
28
export const provideAll = <R>(r: R) => <E, A>(
29
ma: Effect<R, E, A>
30
): Effect<NoEnv, E, A>
31
32
// provide environment effectfuly (only top-level)
33
export const provideM = <R2, R, E2>(
34
f: Effect<R2, E2, R>
35
) => <E, A>(ma: Effect<R, E, A>): Effect<R2, E | E2, A>
36
37
// provide part of environment effectfuly (only top-level)
38
export const provideSomeM = <R2, R, E2>(
39
f: Effect<R2, E2, R>
40
) => <E, A, R3>(ma: Effect<R & R3, E, A>): Effect<R2 & R3, E | E2, A>
Copied!

Usage

1
import { effect as T, exit as E } from "@matechs/effect";
2
import { pipe } from "fp-ts/lib/pipeable";
3
4
// utility to handle exit
5
const foldExit = <A>(onDone: (a: A) => void) =>
6
E.fold(
7
onDone,
8
e => console.error("error:", e),
9
e => console.error("abort:", e),
10
() => console.error("interrupt")
11
);
12
13
// unique symbol to hold an environment for app configuration
14
const appConfigEnv: unique symbol = Symbol();
15
16
// describe the AppConfig environment entry
17
interface AppConfig {
18
[appConfigEnv]: {
19
appName: string;
20
};
21
}
22
23
// implement the live version of the config
24
const appConfigLive: AppConfig = {
25
[appConfigEnv]: {
26
appName: "My First App"
27
}
28
};
29
30
// utility to access the config
31
function appName(): T.Effect<AppConfig, never, string> {
32
return T.access(({ [appConfigEnv]: { appName } }: AppConfig) => appName);
33
}
34
35
// unique symbol to hold an environment for console effect
36
const consoleEnv: unique symbol = Symbol();
37
38
// describe the console environmental effect
39
interface Console {
40
[consoleEnv]: {
41
log: (s: string) => T.Effect<T.NoEnv, T.NoErr, void>;
42
};
43
}
44
45
// live implementation of the console effect
46
const consoleLive: Console = {
47
[consoleEnv]: {
48
log: s =>
49
T.sync(() => {
50
console.log(s);
51
})
52
}
53
};
54
55
// utility to access the console log through the environment
56
function log(s: string): T.Effect<Console, never, void> {
57
return T.accessM(({ [consoleEnv]: { log } }: Console) => log(s));
58
}
59
60
// log the app name, combine the environment requirements
61
const program: T.Effect<Console & AppConfig, never, void> = pipe(
62
appName(),
63
T.chain(log)
64
);
65
66
// construct the live environment
67
const live = pipe(
68
T.noEnv,
69
T.mergeEnv(appConfigLive),
70
T.mergeEnv(consoleLive)
71
);
72
73
T.run(
74
T.provideAll(live)(program), // print: My First App
75
foldExit(() => {
76
// no output
77
})
78
);
Copied!

Notes

Type signatures of chain and all the main combinators have been refined to merge environment requirements so you can keep environment requirements on each function as small as possible as the required environment of a function is the environment you will have to provide for test purposes.
When you use environmental effects for everything you easily get to the point of having very long chains of combined environments, it is useful not to restrict the return type of your functions from the beginning and progressively type as you go. Type aliases can significantly reduce this "problem" and make consumer usage more friendly.
An example of the above can be seen in the express package where environment aliases have been exposed in a combined way for the consumer while internally kept more granular (granularity can help further with progressive providing of environment see for example the route combinator in the express package)
Last modified 1yr ago
Copy link