Effect.ts: Absence as First-Class

Dependency InjectionLesson 19 of 51

19. Services & Context - Providing Absent Dependencies

Remember the 'Requirements' in EffectA, E, R? Here's how to provide them.

Remember the 'Requirements' in Effect<A, E, R>? Here's how to provide them.

Code Example
// Define a service interface
class Database extends Context.Tag("Database")<
  Database,
  {
    query: (sql: string) => Effect<Result, DbError>
  }
>() {}

// Use it (doesn't exist yet!)
const getUser = (id: string) =>
  Effect.gen(function* () {
    const db = yield* Database;  // "I need Database"
    return yield* db.query(`SELECT * WHERE id=${id}`);
  });

// Type: Effect<User, DbError, Database>
//                             ^^^^^^^^
//                             Requires Database!

// Provide it (NOW it can run)
const program = getUser("123").pipe(
  Effect.provide(DatabaseLive)
);
Interactive Example
const output: string[] = [];
output.push('class Database extends Context.Tag("Database") {...}');
output.push('');
output.push('Effect requires Database in type signature:');
output.push('  Effect<User, DbError, Database>');
output.push('                        ^^^^^^^^');
output.push('');
output.push('Must provide before running:');
output.push('  getUser("123").pipe(Effect.provide(DatabaseLive))');
output.push('');
output.push(' Services are typed requirements!');
return output.join('
');
Explanation

Services: typing absent dependencies in the Effect signature.

Traditional approach: assume services exist (imports, globals). Effect types service unavailability:

  • User is unavailable
  • Database is unavailable (typed in Effect<A,E,R>)
  • No assumptions about Database existing
  • Must explicitly provide it

Absence-first dependency injection: services are typed as absent!


Part 19 of 51 in the Effect.ts Absence Modeling series