Skip to content

revskill10/elmora.js

Repository files navigation

Elmora.js

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.

Features

  • 🔍 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

Installation

npm install elmora.js

Quick Start

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]

Core Modules

Result Monad

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])

Option Type

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'

Pattern Matching

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();

Lazy Lists

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);

Functional Composition

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

Process System

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

Advanced Features

Effect System

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)
);

Metaprogramming

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;
  }
});

Supervision Trees

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' })
]);

Type Safety

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

Performance

  • 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

Examples

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

Documentation

Contributing

Contributions welcome! Please read CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE for details.

About

Functional programming for JS

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published