A type-safe, functional, performant, lawful, composable data structure that solves practical problems of effect-full code in node and browser.
npm i fearless-io
import {IO, defaultRuntime} from 'fearless-io'
// Create a pure version of `console.log` called `putStrLn`
const putStrLn = IO.encase((str: string) => console.log(str))
const hello = putStrLn('Hello World!')
const runtime = defaultRuntime()
runtime.unsafeExecute(hello)
interface FIO<R, E, A> {
// ... Operators
}
FIO
takes in three type params viz. —
R
Represents the type of environment needed to execute this IO (more).E
The error types that can be emitted while this IO is executing.A
The type of the success value that will be emitted by the IO on completion.
Using these three type params you can fairly represent any side-effect. For example lets say there is function Greet
which simply prints "Hello World" —
const Greet = () => console.log('Hello World!')
To represent Greet
—
R
could beunknown
: sinceconsole.log
works everywhere.E
could benever
: Printing anything on console never fails.A
could bevoid
: The output of running the program is basically nothing.
const GreetIO: FIO<unknown, never, void>
There are multiple ways through which you can create an instance of FIO viz. FIO.from
or FIO.encase
etc. Refer to the API documentation to learn about all the ways.
Once of the easiest ways to create a FIO is through FIO.encase.
+ import {FIO} from 'fearless-io'
const Greet = () => console.log('Hello World!')
+ const GreetIO = FIO.encase(Greet)
Calling GreetIO()
returns a pure data structure which represents a side-effect, that —
- Can execute in any environment without any special needs.
- Never fails.
- Resolves with a
void
.
Execution of FIO happens through a Runtime.
- import {FIO} from 'fearless-io'
+ import {FIO, defaultRuntime} from 'fearless-io'
const Greet = () => console.log('Hello World!')
const GreetIO = FIO.encase(Greet)
+ defaultRuntime().unsafeExecute(GreetIO())
Since these data structures don't specify how or when they are going to be executed, writing them one after the other in procedural style will not guarantee any order of execution, for Eg —
+ import {FIO} from 'fearless-io'
+ const putStrLn = FIO.encase((msg: string) => console.log(msg))
+
+ const foo = putStrLn('foo')
+ const bar = putStrLn('bar')
In the above code either foo
or bar
can be printed first depending on internal prioritization and scheduling algorithms that FIO uses. To ensure that foo
is printed first and bar
is printed second one must use the and operator.
import {FIO} from 'fearless-io'
const putStrLn = FIO.encase((msg: string) => console.log(msg))
const fooIO = putStrLn('foo')
const barIO = putStrLn('bar')
+ const fooBar = fooIO.and(barIO)
fooBar
is a new FIO object of type FIO<unknown, never, void>
.
- import {FIO} from 'fearless-io'
+ import {FIO, defaultRuntime} from 'fearless-io'
const putStrLn = FIO.encase((msg: string) => console.log(msg))
const fooIO = putStrLn('foo')
const barIO = putStrLn('bar')
const fooBar = fooIO.and(barIO)
+ defaultRuntime().unsafeExecute(fooBar)
Similar to the and
operator, the par operator runs the two IOs in parallel. For eg.
Create the two IOs
+ import {FIO} from 'fearless-io'
+
+ const foo = FIO.timeout('foo', 1000)
+ const bar = FIO.timeout('bar', 1500)
Combine them using par
- import {FIO} from 'fearless-io'
const foo = FIO.timeout('foo', 1000)
const bar = FIO.timeout('bar', 1500)
+ const fooBar = foo.par(bar)
Execute the created IO
- import {FIO} from 'fearless-io'
+ import {FIO, defaultRuntime} from 'fearless-io'
const foo = FIO.timeout('foo', 1000)
const bar = FIO.timeout('bar', 1500)
const fooBar = foo.zip(bar)
+ defaultRuntime().unsafeExecute(fooBar)
The program fooBar
will complete in 1500
ms because both are executed in parallel.
Other more powerful operators can be found at API Documentation.
Executing an IO returns a cancel callback. Essentially a function that when called, aborts the IO from any further execution and synchronously releases all the acquired resources.
Create an IO
+ import {FIO} from 'fearless-io'
+ const delayIO = FIO.timeout('Hello World', 1000)
Execute by passing it to defaultRuntime
- import {FIO} from 'fearless-io'
+ import {FIO, defaultRuntime} from 'fearless-io'
const delayIO = FIO.timeout('Hello World', 1000)
+ const cancel = defaultRuntime().unsafeExecute(delayIO)
Calling the cancelling callback.
import {FIO, defaultRuntime} from 'fearless-io'
const delayIO = FIO.timeout('Hello World', 1000)
const cancel = defaultRuntime().execute(delayIO)
+ cancel()
As soon as cancel
is called internally the timeout is cancelled.
By default any FIO instance would not need any env. This can be customized based on what the program needs to perform. For example, if a program needs to read a config
and print out the port
set in it one could do something like this —
Say we already have a Config
interface, with only one property —
+ interface Config {
+ port: number
+ }
Next we create an Environment that returns a config
—
interface Config {
port: number
}
+ interface ConfigEnv {
+ config: Config
+ }
We add getPort
which picks the port
and putStrLn
which is a wrapper over console.log
to make it pure.
+ import {FIO} from 'fearless-io'
interface Config {
port: number
}
interface ConfigEnv {
config: Config
}
+ const getPort = FIO.access((config: Config) => config.port)
+ const putStrLn = FIO.encase((message: string) => console.log(message))
Using the chain operator one can now chain them one after the other —
import {FIO} from 'fearless-io'
interface Config {
port: number
}
interface ConfigEnv {
config: Config
}
const getPort = FIO.access((config: Config) => config.port)
const putStrLn = FIO.encase((message: string) => console.log(message))
+ const program = getPort().chain(putStrLn)
You can provide the env directly to a FIO instance without executing it using the provide method.
import {FIO} from 'fearless-io'
+ import config from 'node-config'
interface Config {
port: number
}
interface ConfigEnv {
config: Config
}
const getPort = FIO.access((config: Config) => config.port)
const putStrLn = FIO.encase((message: string) => console.log(message))
const program = getPort().chain(putStrLn)
+ const env = {
+ config: config
+ }
+ const program0 = program.provide(env)
Running the program can be done by using the runtime.
- import {FIO} from 'fearless-io'
+ import {FIO, defaultRuntime} from 'fearless-io'
import config from 'node-config'
interface Config {
port: number
}
interface ConfigEnv {
config: Config
}
const getPort = FIO.access((config: Config) => config.port)
const putStrLn = FIO.encase((message: string) => console.log(message))
const program = getPort().chain(putStrLn)
const env = {
config: config
}
const program0 = program.provide(env)
+ defaultRuntime().unsafeExecute(program0)
Checkout a fully functional example here.
FIO is heavily inspired by the following libraries —