Micro for Effect Users

5 min read Original article ↗

The Micro module is designed as a lighter alternative to the standard Effect module, tailored for situations where it is beneficial to reduce the bundle size.

This module is standalone and does not include more complex functionalities such as Layer, Ref, Queue, and Deferred. This feature set makes Micro especially suitable for libraries that wish to utilize Effect functionalities while keeping the bundle size to a minimum, particularly for those aiming to provide Promise-based APIs.

Micro also supports use cases where a client application uses Micro, and a server employs the full suite of Effect features, maintaining both compatibility and logical consistency across various application components.

Integrating Micro adds a minimal footprint to your bundle, starting at 5kb gzipped, which may increase depending on the features you use.

Importing Micro

Micro is a part of the Effect library and can be imported just like any other module:

import { Micro } from "effect"

You can also import it using a namespace import like this:

import * as Micro from "effect/Micro"

Both forms of import allow you to access the functionalities provided by the Micro module.

However an important consideration is tree shaking, which refers to a process that eliminates unused code during the bundling of your application. Named imports may generate tree shaking issues when a bundler doesn’t support deep scope analysis.

Here are some bundlers that support deep scope analysis and thus don’t have issues with named imports:

  • Rollup
  • Webpack 5+

Main Types

Micro

The Micro type uses three type parameters:

┌─── Represents the success type

│ ┌─── Represents the error type

│ │ ┌─── Represents required dependencies

▼ ▼ ▼

Micro<Success, Error, Requirements>

which mirror those of the Effect type.

MicroExit

The MicroExit type is a streamlined version of the Exit type, designed to capture the outcome of a Micro computation. It can either be successful, containing a value of type A, or it can fail, containing an error of type E wrapped in a MicroCause.

type MicroExit<A, E> = MicroExit.Success<A, E> | MicroExit.Failure<A, E>

MicroCause

The MicroCause type is a streamlined version of the Cause type.

Similar to how Cause is a union of types, MicroCause comes in three forms:

type MicroCause<E> = Die | Fail<E> | Interrupt

VariantDescription
DieIndicates an unforeseen defect that wasn’t planned for in the system’s logic.
Fail<E>Covers anticipated errors that are recognized and typically handled within the application.
InterruptSignifies an operation that has been purposefully stopped.

MicroSchedule

The MicroSchedule type is a streamlined version of the Schedule type.

type MicroSchedule = (attempt: number, elapsed: number) => Option<number>

Represents a function that can be used to calculate the delay between repeats.

The function takes the current attempt number and the elapsed time since the first attempt, and returns the delay for the next attempt. If the function returns None, the repetition will stop.

How to Use This Guide

Below, you’ll find a series of comparisons between the functionalities of Effect and Micro. Each table lists a functionality of Effect alongside its counterpart in Micro. The icons used have the following meanings:

  • ⚠️: The feature is available in Micro, but with some differences from Effect.
  • ❌: The feature is not available in Effect.

Creating Effects

EffectMicro⚠️
Effect.tryMicro.tryrequires a try block
Effect.tryPromiseMicro.tryPromiserequires a try block
Effect.sleepMicro.sleeponly handles milliseconds
Effect.failCauseMicro.failWithuses MicroCause instead of Cause
Effect.failCauseSyncMicro.failWithSyncuses MicroCause instead of Cause
Micro.make
Micro.fromOption
Micro.fromEither

Running Effects

EffectMicro⚠️
Effect.runSyncExitMicro.runSyncExitreturns a MicroExit instead of an Exit
Effect.runPromiseExitMicro.runPromiseExitreturns a MicroExit instead of an Exit
Effect.runForkMicro.runForkreturns a MicroFiber instead of a RuntimeFiber

runSyncExit

The Micro.runSyncExit function is used to execute an Effect synchronously, which means it runs immediately and returns the result as a MicroExit.

Example (Handling Results as MicroExit)

import { Micro } from "effect"

const result1 = Micro.runSyncExit(Micro.succeed(1))

console.log(result1)

/*

Output:

{

"_id": "MicroExit",

"_tag": "Success",

"value": 1

}

*/

const result2 = Micro.runSyncExit(Micro.fail("my error"))

console.log(result2)

/*

Output:

{

"_id": "MicroExit",

"_tag": "Failure",

"cause": {

"_tag": "Fail",

"traces": [],

"name": "MicroCause.Fail",

"error": "my error"

}

}

*/

runPromiseExit

The Micro.runPromiseExit function is used to execute an Effect and obtain the result as a Promise that resolves to a MicroExit.

Example (Handling Results as MicroExit)

import { Micro } from "effect"

Micro.runPromiseExit(Micro.succeed(1)).then(console.log)

/*

Output:

{

"_id": "MicroExit",

"_tag": "Success",

"value": 1

}

*/

Micro.runPromiseExit(Micro.fail("my error")).then(console.log)

/*

Output:

{

"_id": "MicroExit",

"_tag": "Failure",

"cause": {

"_tag": "Fail",

"traces": [],

"name": "MicroCause.Fail",

"error": "my error"

}

}

*/

runFork

The Micro.runFork function executes the effect and return a MicroFiber that can be awaited, joined, or aborted.

You can listen for the result by adding an observer using the addObserver method.

Example (Observing an Asynchronous Effect)

import { Micro } from "effect"

// ┌─── MicroFiber<number, never>

// ▼

const fiber = Micro.succeed(42).pipe(Micro.delay(1000), Micro.runFork)

// Attach an observer to log the result when the effect completes

fiber.addObserver((result) => {

console.log(result)

})

console.log("observing...")

/*

Output:

observing...

{

"_id": "MicroExit",

"_tag": "Success",

"value": 42

}

*/

Building Pipelines

EffectMicro⚠️
Effect.andThenMicro.andThendoesn’t handle Promise or () => Promise as argument
Effect.tapMicro.tapdoesn’t handle () => Promise as argument
Effect.allMicro.allno batching and mode options
Effect.forEachMicro.forEachno batching option
Effect.filterMicro.filterno batching option
Effect.filterMapMicro.filterMapthe filter is effectful

Expected Errors

EffectMicro⚠️
Effect.exitMicro.exitreturns a MicroExit instead of an Exit

Unexpected Errors

EffectMicro
Micro.catchCauseIf

Timing Out

EffectMicro
Micro.timeoutOrElse

Requirements Management

To access a service while using Micro.gen, you need to wrap the service tag using the Micro.service function:

Example (Accessing a Service in Micro.gen)

import { Micro, Context } from "effect"

class Random extends Context.Tag("MyRandomService")<

Random,

{ readonly next: Micro.Micro<number> }

>() {}

const program = Micro.gen(function* () {

// const random = yield* Random // this doesn't work

const random = yield* Micro.service(Random)

const randomNumber = yield* random.next

console.log(`random number: ${randomNumber}`)

})

const runnable = Micro.provideService(program, Random, {

next: Micro.sync(() => Math.random())

})

Micro.runPromise(runnable)

/*

Example Output:

random number: 0.8241872233134417

*/

Scope

EffectMicro⚠️
ScopeMicroScopereturns a MicroScope instead of a Scope
Scope.makeMicro.scopeMakereturns a MicroScope instead of a Scope

Retrying

EffectMicro⚠️
Effect.retryMicro.retrydifferent options

Repetition

EffectMicro⚠️
Effect.repeatMicro.repeatdifferent options
Micro.repeatExit

Timing out

EffectMicro
Micro.timeoutOrElse

Sandboxing

EffectMicro⚠️
Effect.sandboxMicro.sandboxreturns a MicroCause<E> instead of Cause<E>

Error Channel Operations

EffectMicro⚠️
Micro.filterOrFailWith
Effect.tapErrorCauseMicro.tapErrorCauseMicroCause<E> instead of Cause<E>
Micro.tapCauseIf
Effect.tapDefectMicro.tapDefectunknown instead of Cause<never>

Requirements Management

EffectMicro⚠️
Effect.provideMicro.provideContextonly handles Context
Micro.provideScope
Micro.service

Scoping, Resources and Finalization

EffectMicro⚠️
Effect.addFinalizerMicro.addFinalizerMicroExit instead of Exit and no R
Effect.acquireReleaseMicro.acquireReleaseMicroExit instead of Exit
Effect.acquireUseReleaseMicro.acquireUseReleaseMicroExit instead of Exit
Effect.onExitMicro.onExitMicroExit instead of Exit
Effect.onErrorMicro.onErroruses MicroCause instead of Cause
Micro.onExitIf

Concurrency

EffectMicro⚠️
Effect.forkMicro.forkMicroFiber instead of RuntimeFiber
Effect.forkDaemonMicro.forkDaemonMicroFiber instead of RuntimeFiber
Effect.forkInMicro.forkInMicroFiber instead of RuntimeFiber
Effect.forkScopedMicro.forkScopedMicroFiber instead of RuntimeFiber