tiny-multimethods
Version:
Tiny port of Clojure's multimethods with zero dependencies and 30 LOC.
165 lines (122 loc) âĒ 4.24 kB
Markdown
# tiny-multimethods
A lightweight implementation of Clojure-style multimethods for JavaScript in under 30 lines of code. Define functions that dispatch to different implementations based on runtime values.
## Installation
```bash
npm install tiny-multimethods
```
## Usage
```javascript
import { defmulti, DEFAULT } from 'tiny-multimethods';
// Create a multimethod that dispatches based on shape type
const calculateArea = defmulti(
shape => shape.type,
['circle', shape => Math.PI * shape.radius ** 2],
['rectangle', shape => shape.width * shape.height],
[DEFAULT, shape => {
throw new Error(`Can't calculate area of ${shape.type}`);
}]
);
// Use it
console.log(calculateArea({ type: 'circle', radius: 5 })); // 78.54...
console.log(calculateArea({ type: 'rectangle', width: 4, height: 3 })); // 12
// Add new methods dynamically
calculateArea.addMethod('triangle', shape =>
(shape.base * shape.height) / 2
);
```
## Features
- ðŠķ Lightweight: Less than 30 lines of code
- ðŊ Flexible dispatch: Use any function to determine which method to call
- ð Extensible: Add new methods at runtime
- ð Default cases: Handle unknown values gracefully
## API
### `defmulti(dispatchFn, ...methods)`
Creates a new multimethod using the provided dispatch function and method implementations.
Parameters:
- `dispatchFn`: Function that takes an argument and returns a dispatch value
- `...methods`: Array of `[dispatchValue, implementation]` pairs
Returns: A function that will dispatch to the appropriate method based on the dispatch function's result.
### `DEFAULT`
A special symbol used to define default method implementations.
## Examples
### Content Renderer
```javascript
const renderContent = defmulti(
content => content.type,
['text', content => `<p>${content.value}</p>`],
['image', content => `<img src="${content.url}" alt="${content.alt || ''}">`],
['video', content => `
<video controls>
<source src="${content.url}" type="video/mp4">
</video>
`],
[DEFAULT, content => `<div>Unsupported content type: ${content.type}</div>`]
);
// Usage
renderContent({ type: 'text', value: 'Hello World' });
// <p>Hello World</p>
```
### Priority-based Notifications
```javascript
const notifyUser = defmulti(
notification => notification.priority,
['high', n => {
console.log('ðĻ URGENT:', n.message);
return 'high-priority-handled';
}],
['medium', n => {
console.log('âđïļ NOTICE:', n.message);
return 'medium-priority-handled';
}],
['low', n => {
console.log('ð FYI:', n.message);
return 'low-priority-handled';
}]
);
notifyUser({ priority: 'high', message: 'System is down!' });
// ðĻ URGENT: System is down!
```
### Complex Dispatch
```javascript
const calculateShipping = defmulti(
// Dispatch based on multiple factors
order => `${order.method}-${order.type}`,
['express-fragile', order => {
const baseCost = order.weight * 0.1 + order.distance * 0.05;
return baseCost * 2.5 + 15; // Extra handling + insurance
}],
['standard-regular', order => {
return order.weight * 0.1 + order.distance * 0.05;
}]
);
```
### Dynamic Method Addition
```javascript
const processor = defmulti(
data => data.version,
['v1', data => ({ ...data, upgraded: true })]
);
// inspect available methods
processor.methods()
// => ['v1']
// Add support for new version later
processor.addMethod('v2', data => ({
...data,
upgraded: true,
timestamp: Date.now()
}));
// observe new method is available
processor.methods()
// => ['v1', 'v2']
```
## Why Use This?
- **Clean Code Organization**: Group related implementations together instead of scattered if/else statements
- **Runtime Extensibility**: Add new behaviors without modifying existing code
- **Simple but Powerful**: Lightweight implementation that handles most common use cases
- **Flexible Dispatch**: Use any function to determine which implementation to use
## Inspiration
This implementation is inspired by Clojure's multimethod system, bringing similar capabilities to JavaScript in a lightweight package.
## License
MIT
## Contributing
Issues and pull requests are welcome! Feel free to contribute to make this tiny utility even better.