Sometimes you are forced to interoperate with code not written in a functional style, let's see how to deal with it.
Sentinels
Use case: an API that may fail and returns a special value of the codomain.
Example: Array.prototype.findIndex
Solution: Option
import { Option, none, some } from 'fp-ts/Option'
function findIndex<A>(
as: Array<A>,
predicate: (a: A) => boolean
): Option<number> {
const index = as.findIndex(predicate)
return index === -1 ? none : some(index)
}
undefined
and null
Use case: an API that may fail and returns undefined
(or null
).
Example: Array.prototype.find
Solution: Option
, fromNullable
import { Option, fromNullable } from 'fp-ts/Option'
function find<A>(as: Array<A>, predicate: (a: A) => boolean): Option<A> {
return fromNullable(as.find(predicate))
}
Exceptions
Use case: an API that may throw.
Example: JSON.parse
Solution: Either
, tryCatch
import { Either, tryCatch } from 'fp-ts/Either'
function parse(s: string): Either<Error, unknown> {
return tryCatch(
() => JSON.parse(s),
(reason) => new Error(String(reason))
)
}
Random values
Use case: an API that returns a non deterministic value.
Example: Math.random
Solution: IO
import { IO } from 'fp-ts/IO'
const random: IO<number> = () => Math.random()
Synchronous side effects
Use case: an API that reads and/or writes to a global state.
Example: localStorage.getItem
Solution: IO
import { Option, fromNullable } from 'fp-ts/Option'
import { IO } from 'fp-ts/IO'
function getItem(key: string): IO<Option<string>> {
return () => fromNullable(localStorage.getItem(key))
}
Use case: an API that reads and/or writes to a global state and may throw.
Example: readFileSync
Solution: IOEither
, tryCatch
import * as fs from 'fs'
import { IOEither, tryCatch } from 'fp-ts/IOEither'
function readFileSync(path: string): IOEither<Error, string> {
return tryCatch(
() => fs.readFileSync(path, 'utf8'),
(reason) => new Error(String(reason))
)
}
Asynchronous side effects
Use case: an API that performs an asynchronous computation.
Example: reading from standard input
Solution: Task
import { createInterface } from 'readline'
import { Task } from 'fp-ts/Task'
const read: Task<string> = () =>
new Promise<string>((resolve) => {
const rl = createInterface({
input: process.stdin,
output: process.stdout
})
rl.question('', (answer) => {
rl.close()
resolve(answer)
})
})
Use case: an API that performs an asynchronous computation and may reject.
Example: fetch
Solution: TaskEither
, tryCatch
import { TaskEither, tryCatch } from 'fp-ts/TaskEither'
function get(url: string): TaskEither<Error, string> {
return tryCatch(
() => fetch(url).then((res) => res.text()),
(reason) => new Error(String(reason))
)
}
Top comments (8)
This is great, thank you. Is there library like Ramda for TS which hides internals of functional programming? Which embraces all of the common functions like in this article into one import statement. With FP-TS one has to know in which lib something belongs to to import it, can be time consuming.
I want too.
Ciao Giulio, I'm now starting to play with your awesome library and there are a couple of things that I just don't get it right , probably because of my poor knowledge in FP.
The first think that surprise me is how to implement the pipe function, for other libraries that I used before pie was defined like:
pipe :: ((a -> b), ..., (y -> z)) -> a -> z
So as an example I'm use to do something like:
And then later pass my data type (IO, Option or any functor) to evaluate the result
but in your library you define it like:
pipe :: (a, (a -> b), ..., (y -> z)) -> z
so I don't see the way to get partially apply in order to do lazy evaluation and I also have to pass the data type as a first argument witch is really strange for me to be in a pipe.
Can you point me to the right direction?
see
flow
gcanti.github.io/fp-ts/modules/fun...This post was super helpful. Clear examples, and gives an overview of different alternatives. And I would never have thought that TaskEither existed unless reading this.
Awesome intro article!
Is there a chance we could get the table of contents for the series kind of like you have it in dev.to/gcanti/functional-design-co...?
Hey great article.
But i'm not seeing any examples of how to use these functions?
i'm struggling with understanding how these things all come together
thanks
Apologies, I'm new to JS and TS. Where have we declared type classes for Lazy and Some or is it in native js?