Fp refactor
Awesome Claude Skills, Tools for Customizing Claude AI workflows
npx -y skills add ranbot-ai/awesome-skills --skill fp-refactorAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Comprehensive guide for refactoring imperative TypeScript code to fp-ts functional patterns
SKILL.md
5.2 KB, as published. Nobody here has run it
Refactoring Imperative Code to fp-ts
This skill provides comprehensive patterns and strategies for migrating existing imperative TypeScript code to fp-ts functional programming patterns.
When to Use
- You are refactoring an existing imperative TypeScript codebase toward fp-ts patterns.
- The task involves converting
try/catch, null checks, callbacks, DI, or loops into functional equivalents. - You need migration guidance and tradeoffs, not just isolated fp-ts examples.
Table of Contents
- Converting try-catch to Either/TaskEither
- Converting null checks to Option
- Converting callbacks to Task
- Converting class-based DI to Reader
- Converting imperative loops to functional operations
- Migrating Promise chains to TaskEither
- Common Pitfalls
- Gradual Adoption Strategies
- When NOT to Refactor
1. Converting try-catch to Either/TaskEither
The Problem with try-catch
Traditional try-catch blocks have several issues:
- Error handling is implicit and easy to forget
- The type system doesn't track which functions can throw
- Control flow is non-linear and harder to reason about
- Composing multiple fallible operations is verbose
Pattern: Synchronous try-catch to Either
Before (Imperative)
function parseJSON(input: string): unknown {
try {
return JSON.parse(input);
} catch (error) {
throw new Error(`Invalid JSON: ${error}`);
}
}
function validateUser(data: unknown): User {
try {
if (!data || typeof data !== 'object') {
throw new Error('Data must be an object');
}
const obj = data as Record<string, unknown>;
if (typeof obj.name !== 'string') {
throw new Error('Name is required');
}
if (typeof obj.age !== 'number') {
throw new Error('Age must be a number');
}
return { name: obj.name, age: obj.age };
} catch (error) {
throw error;
}
}
// Usage with nested try-catch
function processUserInput(input: string): User | null {
try {
const data = parseJSON(input);
const user = validateUser(data);
return user;
} catch (error) {
console.error('Failed to process user:', error);
return null;
}
}
After (fp-ts Either)
import * as E from 'fp-ts/Either';
import * as J from 'fp-ts/Json';
import { pipe } from 'fp-ts/function';
interface User {
name: string;
age: number;
}
// Use Json.parse which returns Either<Error, Json>
const parseJSON = (input: string): E.Either<Error, unknown> =>
pipe(
J.parse(input),
E.mapLeft((e) => new Error(`Invalid JSON: ${e}`))
);
// Validation returns Either, making errors explicit in types
const validateUser = (data: unknown): E.Either<Error, User> => {
if (!data || typeof data !== 'object') {
return E.left(new Error('Data must be an object'));
}
const obj = data as Record<string, unknown>;
if (typeof obj.name !== 'string') {
return E.left(new Error('Name is required'));
}
if (typeof obj.age !== 'number') {
return E.left(new Error('Age must be a number'));
}
return E.right({ name: obj.name, age: obj.age });
};
// Compose with pipe and flatMap - errors propagate automatically
const processUserInput = (input: string): E.Either<Error, User> =>
pipe(
parseJSON(input),
E.flatMap(validateUser)
);
// Handle both cases explicitly
pipe(
processUserInput('{"name": "Alice", "age": 30}'),
E.match(
(error) => console.error('Failed to process user:', error.message),
(user) => console.log('User:', user)
)
);
Step-by-Step Refactoring Guide
- Identify the error type: Determine what errors can occur and create appropriate error types
- Change return type: From
TtoEither<E, T>whereEis your error type - Replace throw statements: Convert
throw new Error(...)toE.left(new Error(...)) - Replace return statements: Convert
return valuetoE.right(value) - Remove try-catch blocks: They're no longer needed
- Update callers: Use
pipewithE.flatMapto chain operations
Pattern: Async try-catch to TaskEither
Before (Imperative)
async function fetchUser(id: string): Promise<User> {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return validateUser(data);
} catch (error) {
throw new Error(`Failed to fetch user: ${error}`);
}
}
async function fetchUserPosts(userId: string): Promise<Post[]> {
try {
const response = await fetch(`/api/users/${userId}/posts`);
if (!response.ok) {