fearless-io
v15.0.0A type-safe, functional, performant, lawful, composable data structure that solves practical problems of effect-full code in node and browser.
Index
Installation
npm i fearless-io
Usage
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)
Getting Started
Type Signature
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>
Creating a FIO
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
.
Executing FIO
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())
Serial Execution
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)
Parallel Execution
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.
Cancellation
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.
Custom Environment
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 —
Config
Say we already have a Config
interface, with only one property —
+ interface Config {
+ port: number
+ }
ConfigEnv
Next we create an Environment that returns a config
—
interface Config {
port: number
}
+ interface ConfigEnv {
+ config: Config
+ }
Helper functions
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))
Create program
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)
Provide Env
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
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)
Next Steps
Checkout a fully functional example here.
Credits
FIO is heavily inspired by the following libraries —
Metadata
- ISC
- Whatever
- Tushar Mathur
- released 10/12/2019