Railway Programming in TS ​
Railway programming is concept popularized by Scott Wlaschin in F# for fun and profit.
I will not paraphrase what he explains, he explains it brilliantly and better than I would.
In TypeScript, there mainly 2 flavors: method chaining and piping. Let’s review them both.
Let’s say we want to compose together those functions, the idea is to:
parse number -> multiply by 3 -> divide by 12
ts
const divideBy = (divider: number) => (n: number) => {
if (divider === 0) return Result.fail('cannot divide by zero')
return n / divider
}
const multiplyBy = (factor: number) => (n: number) => n * factor
const parseNumber = (value: unknown) => {
try {
return Result.ok(parseInt(value))
} catch {
return Result.fail('invalid number')
}
}
Method chaining ​
ts
const result = parseNumber('12345')
.map(multiplyBy(3)) // multiplyBy cannot fail
.flatMap(divideBy(12)) // `.flatMap` because `divideBy` returns a Result.
Libraries:
- purify-ts – named as
Either
- @swan-io/boxed
- neverthrow
Pros:
- For a lot of developers, method chaining is an intuitive API to use.
- Intellisense can guide us.
Cons:
- From my actual experience: you need to own the
Result
class. Any shortcoming on the API and you are doomed. Because you always need to transform to other types, and at some point we were lacking a transformation. You don’t have time to wait for a PR or fork stuff around. - There might be a performance overhead because we are re-creating
Result
object at every chained step.
Piping ​
ts
const result = pipe(
'12345',
parseNumber,
multiplyBy(3),
divideBy(12),
)
Libraries:
- fp-ts
- A bunch of others I am too lazy to find back
Pros:
- Composability: we can transform anything to anything, and add our custom functions to the dance.
- No performance overhead, it’s just functions.
Cons:
- piping is mostly understood by functional developers only.
- Intellisense cannot really guide us.