Elixir-inspired functional programming library for TypeScript/JavaScript
A category-theoretic, type-safe library bringing Elixir's best concepts to the JavaScript ecosystem with robust error handling, pattern matching, concurrent processes, and functional programming primitives.
- 🔍 Pattern Matching with exhaustiveness checking
- 🛡️ Result & Option Monads for safe error handling
- ⚡ Concurrent Process System inspired by Elixir actors
- 🧮 Functional Composition with pipe operators
- 📋 Lazy Lists with infinite sequences
- 🎯 Type-Safe Protocols for ad-hoc polymorphism
- 🏗️ Metaprogramming with AST manipulation
- 🎪 Effect System for capability-safe programming
npm install elmora.js
import { Result, Option, match, pipe, List } from 'elmora.js';
// Error handling with Result
const divide = (a: number, b: number): Result<number, string> =>
b === 0 ? Result.Err('Division by zero') : Result.Ok(a / b);
const calculation = pipe(
divide(10, 2),
Result.chain(x => divide(x, 2)),
Result.map(x => x * 3)
);
// Result.Ok(7.5)
// Pattern matching
const handleResponse = match(calculation)
.with({ _tag: 'Ok' }, ({ value }) => `Success: ${value}`)
.with({ _tag: 'Err' }, ({ error }) => `Error: ${error}`)
.exhaustive();
// Lazy lists
const fibonacci = List.iterate(([a, b]) => [b, a + b], [0, 1])
|> List.map(([a]) => a)
|> List.take(10)
|> List.toArray();
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Handle errors functionally without exceptions:
import { Result } from 'elmora.js';
// Basic usage
const parseNumber = (str: string): Result<number, string> =>
Result.tryCatch(() => {
const num = parseFloat(str);
if (isNaN(num)) throw new Error('Invalid number');
return num;
});
// Chaining operations
const result = pipe(
parseNumber('42'),
Result.chain(n => n > 0 ? Result.Ok(n) : Result.Err('Must be positive')),
Result.map(n => n * 2)
);
// Combining multiple Results
const combined = Result.all([
parseNumber('1'),
parseNumber('2'),
parseNumber('3')
]);
// Result.Ok([1, 2, 3])
Handle nullable values safely:
import { Option } from 'elmora.js';
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
const findUser = (id: number): Option<User> =>
Option.fromNullable(users.find(u => u.id === id));
const userName = pipe(
findUser(1),
Option.map(user => user.name),
Option.getOrElse('Unknown')
);
// 'Alice'
Exhaustive pattern matching with type safety:
import { match, P } from 'elmora.js';
type Status = 'loading' | 'success' | 'error';
const handleStatus = (status: Status, data?: any) =>
match({ status, data })
.with({ status: 'loading' }, () => 'Please wait...')
.with({ status: 'success', data: P.any }, ({ data }) => `Data: ${data}`)
.with({ status: 'error' }, () => 'Something went wrong')
.exhaustive();
Infinite sequences with functional operations:
import { List } from 'elmora.js';
// Infinite range
const evenNumbers = List.range(0)
|> List.filter(n => n % 2 === 0)
|> List.take(5);
// [0, 2, 4, 6, 8]
// Parallel processing
const processNumbers = async (numbers: List<number>) =>
List.parallel(4, async (n) => {
// Simulate async work
await new Promise(r => setTimeout(r, 100));
return n * n;
})(numbers);
Powerful composition utilities:
import { pipe, flow, curry } from 'elmora.js';
// Pipe data through transformations
const transform = pipe(
[1, 2, 3, 4, 5],
arr => arr.filter(n => n % 2 === 0),
arr => arr.map(n => n * n),
arr => arr.reduce((a, b) => a + b, 0)
);
// 20
// Create reusable flows
const processData = flow(
(data: string[]) => data.filter(s => s.length > 3),
arr => arr.map(s => s.toUpperCase()),
arr => arr.sort()
);
// Currying for partial application
const add = curry((a: number, b: number) => a + b);
const addFive = add(5);
const result = addFive(3); // 8
Concurrent actors inspired by Elixir:
import { Process, GenServer } from 'elmora.js';
// Define a simple counter server
const CounterServer = GenServer.define<number, 'increment' | 'get'>({
init: () => 0,
handleCall: (message, state) =>
match(message)
.with('increment', () => GenServer.reply(state + 1, state + 1))
.with('get', () => GenServer.reply(state, state))
.exhaustive(),
});
// Usage
const counter = await CounterServer.start();
await GenServer.call(counter, 'increment'); // 1
await GenServer.call(counter, 'increment'); // 2
const count = await GenServer.call(counter, 'get'); // 2
Type-safe effects with capability tracking:
import { Effect } from 'elmora.js';
// Effects are tracked at the type level
const fetchUser = (id: string): Effect<User, 'Network'> =>
Effect.fromAsync(() => fetch(`/users/${id}`).then(r => r.json()));
const logUser = (user: User): Effect<void, 'IO'> =>
Effect.fromSync(() => console.log(user));
// Compose effects - capabilities are automatically inferred
const program: Effect<void, 'Network' | 'IO'> = pipe(
fetchUser('123'),
Effect.chain(logUser)
);
AST manipulation and code generation:
import { Meta, quote, unquote } from 'elmora.js';
// Quote code as data
const expr = quote`
function ${unquote('factorial')}(n) {
return n === 0 ? 1 : n * factorial(n - 1);
}
`;
// Transform AST
const optimized = Meta.transform(expr, {
CallExpression: (node) => {
// Optimize recursive calls
if (node.callee.name === 'factorial') {
return Meta.wrapTailCall(node);
}
return node;
}
});
Fault-tolerant process hierarchies:
import { Supervisor, Application } from 'elmora.js';
const app = Application.start([
supervisorSpec('main', [
workerSpec('database', DatabaseConnection.start, { restart: 'permanent' }),
workerSpec('cache', CacheServer.start, { restart: 'transient' }),
supervisorSpec('workers', [
workerSpec('worker1', Worker.start),
workerSpec('worker2', Worker.start),
], { strategy: 'one_for_one' })
], { strategy: 'rest_for_one' })
]);
Elmora is built with TypeScript-first design:
// Phantom types for compile-time guarantees
type DatabaseId = string & { readonly __brand: 'DatabaseId' };
type UserId = string & { readonly __brand: 'UserId' };
// The compiler prevents mixing incompatible IDs
const fetchUser = (id: UserId): Effect<User, 'Database'> => /* ... */;
const dbId: DatabaseId = 'db_123' as DatabaseId;
// fetchUser(dbId); // ❌ Type error!
// Exhaustiveness checking in pattern matching
type Shape =
| { type: 'circle'; radius: number }
| { type: 'rectangle'; width: number; height: number };
const area = (shape: Shape) =>
match(shape)
.with({ type: 'circle' }, ({ radius }) => Math.PI * radius ** 2)
.with({ type: 'rectangle' }, ({ width, height }) => width * height)
.exhaustive(); // ✅ Compiler ensures all cases are handled
- Lazy evaluation for lists and sequences
- Structural sharing in immutable data structures
- Parallel processing with worker pools
- Efficient pattern matching with compiled decision trees
- Zero-cost abstractions where possible
See the examples/ directory for complete applications:
- Web Scraper - Concurrent scraping with rate limiting
- Chat Server - Real-time messaging with process supervision
- Data Pipeline - ETL processing with error recovery
- Task Queue - Background job processing with retries
- HTTP Client - Type-safe API wrapper with caching
Contributions welcome! Please read CONTRIBUTING.md for guidelines.
MIT License - see LICENSE for details.