Swift Functional Pipelines Workflow
Purpose
Shape Swift data flow as readable functional pipelines whenever that makes the code easier to reason about, test, and compose.
The preferred feel is railway-oriented Swift: values move through named transforms, optional or fallible branches stay explicit, and each stage has one job.
When To Use
- Use this skill for Swift data modeling, parser, loader, transformer, validation, normalization, or export flows.
- Use this skill when code has sprawling loops, nested
if let, duplicated guard ladders, mutable scratch state, callback pyramids, or unclear failure handling. - Use this skill when the user asks for functional Swift, monadic flow, railway-oriented programming, pipelines,
map,flatMap,compactMap,filter,reduce,Result,Optional, orAsyncSequence.
Workflow
- Identify the pipeline boundary:
- input values
- output values
- recoverable failures
- side effects
- async boundaries
- Choose the carrier:
Optionalfor absence when absence is expected and not diagnosticthrowsorasync throwsfor straightforward fallible operationsResultwhen failures must be stored, combined, or passed as valuesAsyncSequencewhen values arrive over time- a small domain pipeline type only when standard carriers cannot express the flow clearly
- hand deeper error carrier, typed throws, domain error, Cocoa bridging, or diagnostics decisions to
swift-error-handling-style-workflow
- Refactor toward composable stages:
- parse
- validate
- normalize
- enrich
- transform
- persist or emit
- Use fluent transforms where they read left-to-right:
mapflatMapcompactMapfilterreducezipforEach- key-path mapping
- Name intermediate values when needed:
- before important diagnostics
- before side effects
- before async boundaries
- when a chain wraps past the point where it remains readable
- Keep side effects at the edge:
- isolate I/O, logging, database writes, UI updates, and network calls
- keep pure transforms independently testable
- keep MainActor and event-loop boundaries explicit
Monadic Defaults
- Keep data modeling and pipelines monadic unless it is genuinely infeasible or harmful.
- Prefer binding fallible stages through
flatMap,throws,Result.flatMap, orasync throwsrather than scattering state mutation across branches. - Prefer small transformations that can be composed, reordered, and tested.
- Prefer a narrow domain type over a loose dictionary when stages need shared meaning.
- Prefer a boring
forloop when mutation is local, performance-sensitive, or more readable than a forced chain.
Output Shape
Return:
Pipeline state: current inputs, outputs, failures, and side effects.Carrier choice: whyOptional,Result,throws,async throws,AsyncSequence, or an imperative fallback fits.Refactor plan: stages and files to change.Example shape: a compact code sketch or call-site sketch.Validation: tests or compile checks that prove the flow still works.
Guardrails
- Do not force point-free or overly abstract style when named functions would be clearer.
- Do not hide side effects inside innocent-looking transforms.
- Do not turn a simple local loop into a chain if the loop is more readable.
- Do not erase useful failure information to fit
Optional. - Do not add a custom monad or pipeline framework unless standard Swift tools are materially insufficient.