Refactoring Imperative Code to fp-ts
Refactor existing imperative TypeScript toward fp-ts without turning the codebase into a puzzle. Use this entrypoint to choose the right functional pattern, then load the full guidance for detailed before-and-after examples.
When to Use
Converting try/catch, thrown errors, Promise chains, callbacks, null checks, loops, or service classes into fp-ts equivalents.
Planning a gradual fp-ts migration across module boundaries or data access layers.
Reviewing whether a proposed functional refactor improves type safety, composition, and testability.
When Not to Use
The code is trivial, stable, and clearer without fp-ts.
The hot path is performance-critical and allocation-heavy abstractions would hurt measurable performance.
The team maintaining the code does not understand fp-ts and there is no migration/training plan.
Core Workflow
Identify the current imperative pain: exceptions, nullable values, async composition, callbacks, loops, or dependency injection.
Refactor at the boundary first so types advertise failure, absence, async work, or dependency requirements explicitly.
Convert sync failures to Either, async failures to TaskEither, nullable values to Option, callbacks to Task, and injected services to Reader when the added structure pays for itself.
Use pipe, map, chain/flatMap, traverse, and sequence to compose operations without nesting.
Add tests around behavior before and after the refactor, especially left/error cases and None/empty paths.
Stop refactoring where the functional version becomes less readable than the imperative original.
Reference Map
Read references/full-guidance.md when the task needs the complete examples and edge cases. It includes:
try/catch to Either/TaskEither, including helper utilities and async examples.
null/undefined to Option and Option/Either conversion patterns.
callbacks to Task, class-based dependency injection to Reader, and loop refactors to map/filter/reduce/flatMap.
Promise chains, Promise.all/race migration, common pitfalls, gradual adoption strategy, and when not to refactor.
Safety and Quality Rules
Do not mix async/await and TaskEither in ways that hide errors outside the type system.
Do not erase useful error details when mapping thrown exceptions into typed errors.
Avoid broad rewrites; migrate one boundary or module at a time and keep bridge functions for callers.
Progressive Loading
Start with this entrypoint for routing and planning. Load references/full-guidance.md only after the task clearly requires deep implementation detail, code examples, validation checklists, or troubleshooting guidance.
1---2name: fp-refactor3description: Use when refactoring imperative TypeScript into fp-ts patterns such as Either, TaskEither, Option, Reader, traversal, and composable pipelines.4license: MIT5---67# Refactoring Imperative Code to fp-ts89Refactor existing imperative TypeScript toward fp-ts without turning the codebase into a puzzle. Use this entrypoint to choose the right functional pattern, then load the full guidance for detailed before-and-after examples.1011## When to Use1213- Converting try/catch, thrown errors, Promise chains, callbacks, null checks, loops, or service classes into fp-ts equivalents.1415- Planning a gradual fp-ts migration across module boundaries or data access layers.1617- Reviewing whether a proposed functional refactor improves type safety, composition, and testability.1819## When Not to Use2021- The code is trivial, stable, and clearer without fp-ts.2223- The hot path is performance-critical and allocation-heavy abstractions would hurt measurable performance.2425- The team maintaining the code does not understand fp-ts and there is no migration/training plan.2627## Core Workflow28291. Identify the current imperative pain: exceptions, nullable values, async composition, callbacks, loops, or dependency injection.30312. Refactor at the boundary first so types advertise failure, absence, async work, or dependency requirements explicitly.32333. Convert sync failures to Either, async failures to TaskEither, nullable values to Option, callbacks to Task, and injected services to Reader when the added structure pays for itself.34354. Use pipe, map, chain/flatMap, traverse, and sequence to compose operations without nesting.36375. Add tests around behavior before and after the refactor, especially left/error cases and None/empty paths.38396. Stop refactoring where the functional version becomes less readable than the imperative original.4041## Reference Map4243Read `references/full-guidance.md` when the task needs the complete examples and edge cases. It includes:4445- try/catch to Either/TaskEither, including helper utilities and async examples.4647- null/undefined to Option and Option/Either conversion patterns.4849- callbacks to Task, class-based dependency injection to Reader, and loop refactors to map/filter/reduce/flatMap.5051- Promise chains, Promise.all/race migration, common pitfalls, gradual adoption strategy, and when not to refactor.5253## Safety and Quality Rules5455- Do not mix async/await and TaskEither in ways that hide errors outside the type system.5657- Do not erase useful error details when mapping thrown exceptions into typed errors.5859- Avoid broad rewrites; migrate one boundary or module at a time and keep bridge functions for callers.6061## Progressive Loading6263Start with this entrypoint for routing and planning. Load `references/full-guidance.md` only after the task clearly requires deep implementation detail, code examples, validation checklists, or troubleshooting guidance.