レイヤーのメモ化
レイヤーのメモ化により、レイヤーは一度作成され、依存関係グラフ内で複数回使用されることが可能になります。例えば、同じレイヤーを二回使用する場合は次のようになります。
Layer.merge(Layer.provide(b, a), Layer.provide(c, a));この場合、aレイヤーは一度だけ割り当てられます。
グローバルに提供する際のメモ化
Effect アプリケーションの重要な特徴の一つは、レイヤーがデフォルトで共有されるということです。つまり、同じレイヤーが二回使用され、かつそのレイヤーがグローバルに提供される場合、レイヤーは一度だけ割り当てられます。依存関係グラフ内のすべてのレイヤーは、そのレイヤーに依存するレイヤー間で共有される一つのインスタンスです。
例えば、サービス A、B、および C の 3 つがあると仮定します。B と C の実装は A サービスに依存しています:
import { Effect, Context, Layer } from "effect";
class A extends Context.Tag("A")<A, { readonly a: number }>() {}
class B extends Context.Tag("B")<B, { readonly b: string }>() {}
class C extends Context.Tag("C")<C, { readonly c: boolean }>() {}
const a = Layer.effect( A, Effect.succeed({ a: 5 }).pipe(Effect.tap(() => Effect.log("initialized"))));
const b = Layer.effect( B, Effect.gen(function* () { const { a } = yield* A; return { b: String(a) }; }));
const c = Layer.effect( C, Effect.gen(function* () { const { a } = yield* A; return { c: a > 0 }; }));
const program = Effect.gen(function* () { yield* B; yield* C;});
const runnable = Effect.provide( program, Layer.merge(Layer.provide(b, a), Layer.provide(c, a)));
Effect.runPromise(runnable);/*出力:timestamp=... level=INFO fiber=#2 message=initialized*/b および c の両方のレイヤーが a レイヤーを要求していますが、a レイヤーは一度だけインスタンス化されます。これは b と c の両方で共有されます。
新しいバージョンの取得
モジュールを共有したくない場合は、Layer.fresh を使用して新しい非共有バージョンを作成するべきです。
import { Effect, Context, Layer } from "effect";
class A extends Context.Tag("A")<A, { readonly a: number }>() {}
class B extends Context.Tag("B")<B, { readonly b: string }>() {}
class C extends Context.Tag("C")<C, { readonly c: boolean }>() {}
const a = Layer.effect( A, Effect.succeed({ a: 5 }).pipe(Effect.tap(() => Effect.log("initialized"))));
const b = Layer.effect( B, Effect.gen(function* () { const { a } = yield* A; return { b: String(a) }; }));
const c = Layer.effect( C, Effect.gen(function* () { const { a } = yield* A; return { c: a > 0 }; }));
const program = Effect.gen(function* () { yield* B; yield* C;});
// ---cut---const runnable = Effect.provide( program, Layer.merge( Layer.provide(b, Layer.fresh(a)), Layer.provide(c, Layer.fresh(a)) ));
Effect.runPromise(runnable);/*出力:timestamp=... level=INFO fiber=#2 message=initializedtimestamp=... level=INFO fiber=#3 message=initialized*/ローカルに提供する際はメモ化なし
レイヤーをグローバルに提供するのではなく、ローカルに提供した場合、そのレイヤーはデフォルトでメモ化をサポートしません。
以下の例では、a レイヤーをローカルに二回提供しましたが、Effect は a レイヤーの構築をメモ化しません。そのため、二回初期化されます:
import { Effect, Context, Layer } from "effect";
class A extends Context.Tag("A")<A, { readonly a: number }>() {}
const a = Layer.effect( A, Effect.succeed({ a: 5 }).pipe(Effect.tap(() => Effect.log("initialized"))));
const program = Effect.gen(function* () { yield* Effect.provide(A, a); yield* Effect.provide(A, a);});
Effect.runPromise(program);/*出力:timestamp=... level=INFO fiber=#0 message=initializedtimestamp=... level=INFO fiber=#0 message=initialized*/手動によるメモ化
Layer.memoize 演算子を使用して a レイヤーを手動でメモ化することができます。これはスコープ付きのエフェクトを返し、そのエフェクトが評価されると、このレイヤーの遅延計算された結果を返します:
import { Effect, Context, Layer } from "effect";
class A extends Context.Tag("A")<A, { readonly a: number }>() {}
const a = Layer.effect( A, Effect.succeed({ a: 5 }).pipe(Effect.tap(() => Effect.log("initialized"))));
const program = Effect.scoped( Layer.memoize(a).pipe( Effect.andThen((memoized) => Effect.gen(function* () { yield* Effect.provide(A, memoized); yield* Effect.provide(A, memoized); }) ) ));
Effect.runPromise(program);/*出力:timestamp=... level=INFO fiber=#0 message=initialized*/