[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-2462c876-c20f-47fc-920f-b4615b128738":3,"$fuVDU9ke_h2z5SbicLXg5gRh1EhZEe2MeM3qX3is5bgU":42},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":33},"2462c876-c20f-47fc-920f-b4615b128738","fp-pragmatic","一本实用、无术语的函数式编程指南——80\u002F20的方法，无需学术负担即可取得成果","cat_coding_backend","mod_coding","sickn33,coding","---\nname: fp-pragmatic\ndescription: A practical, jargon-free guide to functional programming - the 80\u002F20 approach that gets results without the academic overhead\nrisk: unknown\nsource: community\nversion: 1.0.0\nauthor: kadu\ntags:\n  - fp-ts\n  - functional-programming\n  - typescript\n  - pragmatic\n  - beginner-friendly\n  - best-practices\n---\n\n# Pragmatic Functional Programming\n\n**Read this first.** This guide cuts through the academic jargon and shows you what actually matters. No category theory. No abstract nonsense. Just patterns that make your code better.\n\n## When to Use\n- You want a pragmatic starting point for fp-ts or functional programming in TypeScript.\n- The task is exploratory or educational and needs an 80\u002F20 view of what is actually worth adopting.\n- You need guidance on when FP helps and when it is better to keep code simple.\n\n## The Golden Rule\n\n> **If functional programming makes your code harder to read, don't use it.**\n\nFP is a tool, not a religion. Use it when it helps. Skip it when it doesn't.\n\n---\n\n## The 80\u002F20 of FP\n\nThese five patterns give you most of the benefits. Master these before exploring anything else.\n\n### 1. Pipe: Chain Operations Clearly\n\nInstead of nesting function calls or creating intermediate variables, chain operations in reading order.\n\n```typescript\nimport { pipe } from 'fp-ts\u002Ffunction'\n\n\u002F\u002F Before: Hard to read (inside-out)\nconst result = format(validate(parse(input)))\n\n\u002F\u002F Before: Too many variables\nconst parsed = parse(input)\nconst validated = validate(parsed)\nconst result = format(validated)\n\n\u002F\u002F After: Clear, linear flow\nconst result = pipe(\n  input,\n  parse,\n  validate,\n  format\n)\n```\n\n**When to use pipe:**\n- 3+ transformations on the same data\n- You find yourself naming throwaway variables\n- Logic reads better top-to-bottom\n\n**When to skip pipe:**\n- Just 1-2 operations (direct call is fine)\n- The operations don't naturally chain\n\n### 2. Option: Handle Missing Values Without null Checks\n\nStop writing `if (x !== null && x !== undefined)` everywhere.\n\n```typescript\nimport * as O from 'fp-ts\u002FOption'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\n\u002F\u002F Before: Defensive null checking\nfunction getUserCity(user: User | null): string {\n  if (user === null) return 'Unknown'\n  if (user.address === null) return 'Unknown'\n  if (user.address.city === null) return 'Unknown'\n  return user.address.city\n}\n\n\u002F\u002F After: Chain through potential missing values\nconst getUserCity = (user: User | null): string =>\n  pipe(\n    O.fromNullable(user),\n    O.flatMap(u => O.fromNullable(u.address)),\n    O.flatMap(a => O.fromNullable(a.city)),\n    O.getOrElse(() => 'Unknown')\n  )\n```\n\n**Plain language translation:**\n- `O.fromNullable(x)` = \"wrap this value, treating null\u002Fundefined as 'nothing'\"\n- `O.flatMap(fn)` = \"if we have something, apply this function\"\n- `O.getOrElse(() => default)` = \"unwrap, or use this default if nothing\"\n\n### 3. Either: Make Errors Explicit\n\nStop throwing exceptions for expected failures. Return errors as values.\n\n```typescript\nimport * as E from 'fp-ts\u002FEither'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\n\u002F\u002F Before: Hidden failure mode\nfunction parseAge(input: string): number {\n  const age = parseInt(input, 10)\n  if (isNaN(age)) throw new Error('Invalid age')\n  if (age \u003C 0) throw new Error('Age cannot be negative')\n  return age\n}\n\n\u002F\u002F After: Errors are visible in the type\nfunction parseAge(input: string): E.Either\u003Cstring, number> {\n  const age = parseInt(input, 10)\n  if (isNaN(age)) return E.left('Invalid age')\n  if (age \u003C 0) return E.left('Age cannot be negative')\n  return E.right(age)\n}\n\n\u002F\u002F Using it\nconst result = parseAge(userInput)\nif (E.isRight(result)) {\n  console.log(`Age is ${result.right}`)\n} else {\n  console.log(`Error: ${result.left}`)\n}\n```\n\n**Plain language translation:**\n- `E.right(value)` = \"success with this value\"\n- `E.left(error)` = \"failure with this error\"\n- `E.isRight(x)` = \"did it succeed?\"\n\n### 4. Map: Transform Without Unpacking\n\nTransform values inside containers without extracting them first.\n\n```typescript\nimport * as O from 'fp-ts\u002FOption'\nimport * as E from 'fp-ts\u002FEither'\nimport * as A from 'fp-ts\u002FArray'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\n\u002F\u002F Transform inside Option\nconst maybeUser: O.Option\u003CUser> = O.some({ name: 'Alice', age: 30 })\nconst maybeName: O.Option\u003Cstring> = pipe(\n  maybeUser,\n  O.map(user => user.name)\n)\n\n\u002F\u002F Transform inside Either\nconst result: E.Either\u003CError, number> = E.right(5)\nconst doubled: E.Either\u003CError, number> = pipe(\n  result,\n  E.map(n => n * 2)\n)\n\n\u002F\u002F Transform arrays (same concept!)\nconst numbers = [1, 2, 3]\nconst doubled = pipe(\n  numbers,\n  A.map(n => n * 2)\n)\n```\n\n### 5. FlatMap: Chain Operations That Might Fail\n\nWhen each step might fail, chain them together.\n\n```typescript\nimport * as E from 'fp-ts\u002FEither'\nimport { pipe } from 'fp-ts\u002Ffunction'\n\nconst parseJSON = (s: string): E.Either\u003Cstring, unknown> =>\n  E.tryCatch(() => JSON.parse(s), () => 'Invalid JSON')\n\nconst extractEmail = (data: unknown): E.Either\u003Cstring, string> => {\n  if (typeof data === 'object' && data !== null && 'email' in data) {\n    return E.right((data as { email: string }).email)\n  }\n  return E.left('No email field')\n}\n\nconst validateEmail = (email: string): E.Either\u003Cstring, string> =>\n  email.includes('@') ? E.right(email) : E.left('Invalid email format')\n\n\u002F\u002F Chain all steps - if any fails, the whole thing fails\nconst getValidEmail = (input: string): E.Either\u003Cstring, string> =>\n  pipe(\n    parseJSON(input),\n    E.flatMap(extractEmail),\n    E.flatMap(validateEmail)\n  )\n\n\u002F\u002F Success path: Right('user@example.com')\n\u002F\u002F Any failure: Left('specific error message')\n```\n\n**Plain language:** `flatMap` means \"if this succeeded, try the next thing\"\n\n---\n\n## When NOT to Use FP\n\nFunctional programming is not always the answer. Here's when to keep it simple.\n\n### Simple Null Checks\n\n```typescript\n\u002F\u002F Just use optional chaining - it's built into the language\nconst city = user?.address?.city ?? 'Unknown'\n\n\u002F\u002F DON'T overcomplicate it\nconst city = pipe(\n  O.fromNullable(user),\n  O.flatMap(u => O.fromNullable(u.address)),\n  O.flatMap(a => O.fromNullable(a.city)),\n  O.getOrElse(() => 'Unknown')\n)\n```\n\n### Simple Loops\n\n```typescript\n\u002F\u002F A for loop is fine when you need early exit or complex logic\nfunction findFirst(items: Item[], predicate: (i: Item) => boolean): Item | null {\n  for (const item of items) {\n    if (predicate(item)) return item\n  }\n  return null\n}\n\n\u002F\u002F DON'T force FP when it doesn't help\nconst result = pipe(\n  items,\n  A.findFirst(predicate),\n  O.toNullable\n)\n```\n\n### Performance-Critical Code\n\n```typescript\n\u002F\u002F For hot paths, imperative is faster (no intermediate arrays)\nfunction sumLarge(numbers: number[]): number {\n  let sum = 0\n  for (let i = 0; i \u003C numbers.length; i++) {\n    sum += numbers[i]\n  }\n  return sum\n}\n\n\u002F\u002F fp-ts creates intermediate structures\nconst sum = pipe(numbers, A.reduce(0, (acc, n) => acc + n))\n```\n\n### When Your Team Doesn't Know FP\n\nIf you're the only one who can read the code, it's not good code.\n\n```typescript\n\u002F\u002F If your team knows this pattern\nasync function getUser(id: string): Promise\u003CUser | null> {\n  try {\n    const response = await fetch(`\u002Fapi\u002Fusers\u002F${id}`)\n    if (!response.ok) return null\n    return await response.json()\n  } catch {\n    return null\n  }\n}\n\n\u002F\u002F Don't force this on them\nconst getUser = (id: string): TE.TaskEither\u003CError, User> =>\n  pipe(\n    TE.tryCatch(() => fetch(`\u002Fapi\u002Fusers\u002F${id}`), E.toError),\n    TE.flatMap(r => r.ok ? TE.right(r) : TE.left(new Error('Not found'))),\n    TE.flatMap(r => TE.tryCatch(() => r.json(), E.toError))\n  )\n```\n\n---\n\n## Quick Wins: Easy Changes That Improve Code Today\n\n### 1. Replace Nested Ternaries with pipe + fold\n\n```typescript\n\u002F\u002F Before: Nested ternary nightmare\nconst message = user === null\n  ? 'No user'\n  : user.isAdmin\n    ? `Admin: ${user.name}`\n    : `User: ${user.name}`\n\n\u002F\u002F After: Clear case handling\nconst message = pipe(\n  O.fromNullable(user),\n  O.fold(\n    () => 'No user',\n    (u) => u.isAdmin ? `Admin: ${u.name}` : `User: ${u.name}`\n  )\n)\n```\n\n### 2. Replace try-catch with tryCatch\n\n```typescript\n\u002F\u002F Before: try-catch everywhere\nlet config\ntry {\n  config = JSON.parse(rawConfig)\n} catch {\n  config = defaultConfig\n}\n\n\u002F\u002F After: One-liner\nconst config = pipe(\n  E.tryCatch(() => JSON.parse(rawConfig), () => 'parse error'),\n  E.getOrElse(() => defaultConfig)\n)\n```\n\n### 3. Replace undefined Returns with Option\n\n```typescript\n\u002F\u002F Before: Caller might forget to check\nfunction findUser(id: string): User | undefined {\n  return users.find(u => u.id === id)\n}\n\n\u002F\u002F After: Type forces caller to handle missing case\nfunction findUser(id: string): O.Option\u003CUser> {\n  return O.fromNullable(users.find(u => u.id === id))\n}\n```\n\n### 4. Replace Error Strings with Typed Errors\n\n```typescript\n\u002F\u002F Before: Just strings\nfunction validate(data: unknown): E.Either\u003Cstring, User> {\n  \u002F\u002F ...\n  return E.left('validation failed')\n}\n\n\u002F\u002F After: Structured errors\ntype ValidationError = {\n  field: string\n  message: string\n}\n\nfunction validate(data: unknown): E.Either\u003CValidationError, User> {\n  \u002F\u002F ...\n  return E.left({ field: 'email', message: 'Invalid format' })\n}\n```\n\n### 5. Use const Assertions for Error Types\n\n```typescript\n\u002F\u002F Create specific error types without classes\nconst NotFound = (id: string) => ({ _tag: 'NotFound' as const, id })\nconst Unauthorized = { _tag: 'Unauthorized' as const }\nconst ValidationFailed = (errors: string[]) =>\n  ({ _tag: 'ValidationFailed' as const, errors })\n\ntype AppError =\n  | ReturnType\u003Ctypeof NotFound>\n  | typeof Unauthorized\n  | ReturnType\u003Ctypeof ValidationFailed>\n\n\u002F\u002F Now you can pattern match\nconst handleError = (error: AppError): string => {\n  switch (error._tag) {\n    case 'NotFound': return `Item ${error.id} not found`\n    case 'Unauthorized': return 'Please log in'\n    case 'ValidationFailed': return error.errors.join(', ')\n  }\n}\n```\n\n---\n\n## Common Refactors: Before and After\n\n### Callback Hell to Pipe\n\n```typescript\n\u002F\u002F Before\nfetchUser(id, (user) => {\n  if (!user) return handleNoUser()\n  fetchPosts(user.id, (posts) => {\n    if (!posts) return handleNoPosts()\n    fetchComments(posts[0].id, (comments) => {\n      render(user, posts, comments)\n    })\n  })\n})\n\n\u002F\u002F After (with TaskEither for async)\nimport * as TE from 'fp-ts\u002FTaskEither'\n\nconst loadData = (id: string) =>\n  pipe(\n    fetchUser(id),\n    TE.flatMap(user => pipe(\n      fetchPosts(user.id),\n      TE.map(posts => ({ user, posts }))\n    )),\n    TE.flatMap(({ user, posts }) => pipe(\n      fetchComments(posts[0].id),\n      TE.map(comments => ({ user, posts, comments }))\n    ))\n  )\n\n\u002F\u002F Execute\nconst result = await loadData('123')()\npipe(\n  result,\n  E.fold(handleError, ({ user, posts, comments }) => render(user, posts, comments))\n)\n```\n\n### Multiple null Checks to Option Chain\n\n```typescript\n\u002F\u002F Before\nfunction getManagerEmail(employee: Employee): string | null {\n  if (!employee.department) return null\n  if (!employee.department.manager) return null\n  if (!employee.department.manager.email) return null\n  return employee.department.manager.email\n}\n\n\u002F\u002F After\nconst getManagerEmail = (employee: Employee): O.Option\u003Cstring> =>\n  pipe(\n    O.fromNullable(employee.department),\n    O.flatMap(d => O.fromNullable(d.manager)),\n    O.flatMap(m => O.fromNullable(m.email))\n  )\n\n\u002F\u002F Use it\npipe(\n  getManagerEmail(employee),\n  O.fold(\n    () => sendToDefault(),\n    (email) => sendTo(email)\n  )\n)\n```\n\n### Validation with Multiple Checks\n\n```typescript\n\u002F\u002F Before: Throws on first error\nfunction validateUser(data: unknown): User {\n  if (!data || typeof data !== 'object') throw new Error('Must be object')\n  const obj = data as Record\u003Cstring, unknown>\n  if (typeof obj.email !== 'string') throw new Error('Email required')\n  if (!obj.email.includes('@')) throw new Error('Invalid email')\n  if (typeof obj.age !== 'number') throw new Error('Age required')\n  if (obj.age \u003C 0) throw new Error('Age must be positive')\n  return obj as User\n}\n\n\u002F\u002F After: Returns first error, type-safe\nconst validateUser = (data: unknown): E.Either\u003Cstring, User> =>\n  pipe(\n    E.Do,\n    E.bind('obj', () =>\n      typeof data === 'object' && data !== null\n        ? E.right(data as Record\u003Cstring, unknown>)\n        : E.left('Must be object')\n    ),\n    E.bind('email', ({ obj }) =>\n      typeof obj.email === 'string' && obj.email.includes('@')\n        ? E.right(obj.email)\n        : E.left('Valid email required')\n    ),\n    E.bind('age', ({ obj }) =>\n      typeof obj.age === 'number' && obj.age >= 0\n        ? E.right(obj.age)\n        : E.left('Valid age required')\n    ),\n    E.map(({ email, age }) => ({ email, age }))\n  )\n```\n\n### Promise Chain to TaskEither\n\n```typescript\n\u002F\u002F Before\nasync function processOrder(orderId: string): Promise\u003CReceipt> {\n  const order = await fetchOrder(orderId)\n  if (!order) throw new Error('Order not found')\n\n  const validated = await validateOrder(order)\n  if (!validated.success) throw new Error(validated.error)\n\n  const payment = await processPayment(validated.order)\n  if (!payment.success) throw new Error('Payment failed')\n\n  return generateReceipt(payment)\n}\n\n\u002F\u002F After\nconst processOrder = (orderId: string): TE.TaskEither\u003Cstring, Receipt> =>\n  pipe(\n    fetchOrderTE(orderId),\n    TE.flatMap(order =>\n      order ? TE.right(order) : TE.left('Order not found')\n    ),\n    TE.flatMap(validateOrderTE),\n    TE.flatMap(processPaymentTE),\n    TE.map(generateReceipt)\n  )\n```\n\n---\n\n## The Readability Rule\n\nBefore using any FP pattern, ask: **\"Would a junior developer understand this?\"**\n\n### Too Clever (Avoid)\n\n```typescript\nconst result = pipe(\n  data,\n  A.filter(flow(prop('status'), equals('active'))),\n  A.map(flow(prop('value'), multiply(2))),\n  A.reduce(monoid.concat, monoid.empty),\n  O.fromPredicate(gt(threshold))\n)\n```\n\n### Just Right (Prefer)\n\n```typescript\nconst activeItems = data.filter(item => item.status === 'active')\nconst doubledValues = activeItems.map(item => item.value * 2)\nconst total = doubledValues.reduce((sum, val) => sum + val, 0)\nconst result = total > threshold ? O.some(total) : O.none\n```\n\n### The Middle Ground (Often Best)\n\n```typescript\nconst result = pipe(\n  data,\n  A.filter(item => item.status === 'active'),\n  A.map(item => item.value * 2),\n  A.reduce(0, (sum, val) => sum + val),\n  total => total > threshold ? O.some(total) : O.none\n)\n```\n\n---\n\n## Cheat Sheet\n\n| What you want | Plain language | fp-ts |\n|--------------|----------------|-------|\n| Handle null\u002Fundefined | \"Wrap this nullable\" | `O.fromNullable(x)` |\n| Default for missing | \"Use this if nothing\" | `O.getOrElse(() => default)` |\n| Transform if present | \"If something, change it\" | `O.map(fn)` |\n| Chain nullable operations | \"If something, try this\" | `O.flatMap(fn)` |\n| Return success | \"Worked, here's the value\" | `E.right(value)` |\n| Return failure | \"Failed, here's why\" | `E.left(error)` |\n| Wrap throwing function | \"Try this, catch errors\" | `E.tryCatch(fn, onError)` |\n| Handle both cases | \"Do this for error, that for success\" | `E.fold(onLeft, onRight)` |\n| Chain operations | \"Then do this, then that\" | `pipe(x, fn1, fn2, fn3)` |\n\n---\n\n## When to Level Up\n\nOnce comfortable with these patterns, explore:\n\n1. **TaskEither** - Async operations that can fail (replaces Promise + try\u002Fcatch)\n2. **Validation** - Collect ALL errors instead of stopping at first\n3. **Reader** - Dependency injection without classes\n4. **Do notation** - Cleaner syntax for multiple bindings\n\nBut don't rush. The basics here will handle 80% of real-world scenarios. Get comfortable with these before adding more tools to your belt.\n\n---\n\n## Summary\n\n1. **Use pipe** for 3+ operations\n2. **Use Option** for nullable chains\n3. **Use Either** for operations that can fail\n4. **Use map** to transform wrapped values\n5. **Use flatMap** to chain operations that might fail\n6. **Skip FP** when it hurts readability\n7. **Keep it simple** - if your team can't read it, it's not good code\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,72,1357,"2026-05-16 13:18:45",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":25,"skillCount":32,"createdAt":26},"后端开发","backend","mdi-server","API、数据库、服务端架构",296,[34],{"id":35,"skillId":4,"version":36,"fileName":37,"fileSize":38,"filePath":39,"fileHash":40,"manifest":41,"createdAt":19},"068ebd20-3ac4-4643-a8a7-eb483e2d45cb","1.0.0","fp-pragmatic.zip",5771,"uploads\u002Fskills\u002F2462c876-c20f-47fc-920f-b4615b128738\u002Ffp-pragmatic.zip","4316a8f3a6e8abebba7cad2561adbfd03e4a5d813b520926f6a0e1168f3333bf","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":16183}]",{"code":43,"message":44,"data":45},200,"success",{"items":46,"stats":47,"page":50},[],{"averageRating":48,"totalRatings":48,"ratingCounts":49},0,[48,48,48,48,48],{"limit":51,"offset":48,"hasMore":52,"nextOffset":51,"ratedOnly":16},15,false]